course-home.tsx 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752
  1. 'use client';
  2. import { type MouseEvent, type ReactNode } from 'react';
  3. import { ArrowRight, BookOpen, Route, Workflow } from 'lucide-react';
  4. import { courseHref } from './course-navigation';
  5. import { modules } from './module-catalog';
  6. import './course-home.css';
  7. type Navigate = (module: string, lesson?: string) => void;
  8. const moduleDetails = [
  9. {
  10. slug: 'api',
  11. position: '请求入口:浏览器与业务逻辑之间',
  12. responsibility:
  13. '把一个 Python 函数变成别人能调用的 HTTP 接口,约定输入、返回值、错误和通信方式。',
  14. importance:
  15. '接口是前后端的共同约定。边界不清,调用方就不知道请求是否成功、如何处理失败。',
  16. problem:
  17. '参数乱传、错误信息不一致、慢调用卡住服务,以及结果需要逐步展示时怎样通信。',
  18. example:
  19. '商家创建商品、修改名称和价格、切换上架状态;用这些操作练 CRUD,再用商品咨询演示 SSE 与 WebSocket。',
  20. question: '用户的一个操作,怎样成为服务端的一次可靠调用?',
  21. },
  22. {
  23. slug: 'database',
  24. position: '持久数据层:任务与业务记录的真源',
  25. responsibility:
  26. '通过表、约束、索引和事务保存数据;“真源”就是冲突时系统最终以这里的记录为准。',
  27. importance:
  28. '进程会重启、请求会并发。数据库既要让数据留下来,也要守住“哪些状态允许存在”的规则。',
  29. problem:
  30. '数据丢失、重复记录、并发覆盖、慢查询、锁等待,以及备份能否真正恢复。',
  31. example:
  32. '在 shop_lab 从商品与分类需求推导表结构,解释价格为什么按分存、分类为什么拆表,再练事务、索引与恢复。',
  33. question: '服务重启后,哪些事实必须还在?',
  34. },
  35. {
  36. slug: 'api-database',
  37. position: 'API 与 MySQL 的连接处:会话和事务边界',
  38. responsibility:
  39. '把接口接入数据库,安排连接的借用与归还,明确何时提交、何时回滚,以及如何升级表结构。',
  40. importance:
  41. '接口返回“成功”应对应已经提交的事实;重复请求和并发修改不能悄悄破坏业务规则。',
  42. problem:
  43. '连接泄漏、事务跨请求、重复提交、并发更新冲突,以及“记录写了、任务没发出去”的双写窗口。',
  44. example:
  45. '长任务受理时,把任务记录和 outbox 待投递事件写入同一事务;课程用持久化商品服务理解这些边界。',
  46. question: '一次接口成功,到底承诺了哪些数据已经写入?',
  47. },
  48. {
  49. slug: 'tasks',
  50. position: '执行层:任务投递器、队列与独立 worker',
  51. responsibility:
  52. '先区分协程、线程和进程,再把长任务交给独立执行进程;管理状态、并发、重试与取消。',
  53. importance:
  54. '“接口先返回”不会自动带来可靠性。任务能否跨进程退出恢复,要依靠持久记录、投递和确认机制。',
  55. problem:
  56. '请求超时、事件循环阻塞、任务丢失、重复执行、积压,以及 worker 做到一半退出。',
  57. example:
  58. '商家导出商品清单,API 先返回任务 ID,worker 读取商品快照并生成文件;用这条链路练并发、重试和恢复。',
  59. question: '用户不用一直等的工作,之后由谁保证继续执行?',
  60. },
  61. {
  62. slug: 'redis',
  63. position: '加速层:API 旁的可重建缓存',
  64. responsibility:
  65. '把高频读取的结果临时放到 Redis;定义缓存键、有效期、更新失效、容量和故障降级。',
  66. importance:
  67. '缓存让重复读取更快,但可能短暂落后于数据库。先说清可接受的旧数据窗口,才知道怎样使用它。',
  68. problem:
  69. '数据库重复承压、热点同时过期、缓存穿透、内存耗尽,以及缓存不可用时请求雪崩。',
  70. example:
  71. '商品详情被反复读取时使用 Redis 缓存;商家修改商品后处理失效,并以 MySQL 为真源。',
  72. question: '哪些数据可以临时读副本,丢了也能重新构建?',
  73. },
  74. {
  75. slug: 'docker',
  76. position: '运行包装层:API、worker 和依赖服务的容器',
  77. responsibility:
  78. '把代码与依赖构建成镜像,用 Compose 组织服务网络、配置、健康检查、持久卷与资源限制。',
  79. importance:
  80. '同一份代码需要在不同机器上按可重复的方式启动;容器删掉时,数据库数据仍应有独立的保存位置。',
  81. problem:
  82. '本地能跑而部署失败、服务互相找不到、容器重建丢数据、资源争抢,以及升级无法回滚。',
  83. example:
  84. '把 API、worker、MySQL、Redis 作为职责不同的服务运行;课程搭建 shop-service 容器环境。',
  85. question: '怎样把几个进程及其依赖装成可重复运行的一套环境?',
  86. },
  87. {
  88. slug: 'linux',
  89. position: '主机与网络入口:承载进程,对外提供服务',
  90. responsibility:
  91. '管理运行账户、目录权限、端口、systemd、Nginx、TLS、发布回滚和备份恢复流程。',
  92. importance:
  93. '程序能启动只是起点。它还要能被安全访问、在故障后恢复,并且在升级时有明确的退路。',
  94. problem:
  95. '端口不通、权限错误、进程退出、流式响应被代理缓冲、证书配置错误和发布中断。',
  96. example:
  97. '为商品服务的 HTTP 与流式入口配置反向代理,把 shop-service 部署到独立 Linux 练习机。',
  98. question: '从自己的电脑到别人能访问的服务,中间还有哪些工作?',
  99. },
  100. {
  101. slug: 'troubleshooting',
  102. position: '横跨各层:日志、指标、追踪与恢复手册',
  103. responsibility:
  104. '用请求 ID 和任务 ID 串起现场;指标看整体变化,日志看具体事件,追踪看跨组件耗时。',
  105. importance:
  106. '“很慢”只是症状。先找证据和影响范围,才能决定该限流、回滚,还是修复某个依赖。',
  107. problem:
  108. '不知道请求卡在哪、CPU 与连接耗尽、慢 SQL、缓存失效、任务积压,以及修好后无法证明恢复。',
  109. example:
  110. '同一个任务 ID 贯穿 API、投递和 worker,区分“排队久”和“执行慢”;课程进行隔离故障演练。',
  111. question: '用户说任务一直转圈,怎样沿着证据找到卡住的那一段?',
  112. },
  113. {
  114. slug: 'distributed',
  115. position: '设计视角:所有跨进程、跨机器的连接处',
  116. responsibility:
  117. '用架构图与故障推演理解扩容、一致性、复制、分片、消息可靠性和服务治理;练习说明设计取舍。',
  118. importance:
  119. '组件各自正常,不等于协作一定正确。网络超时只能说明没及时收到答复,不能证明对方没执行。',
  120. problem:
  121. '部分失败、重复消息、数据不一致、流量突增、热点、级联故障,以及系统设计题只会堆组件。',
  122. example:
  123. '讨论“任务已完成但响应丢失”“数据库成功但投递失败”等场景;12 节课以理论和面试推演为主。',
  124. question: '多个组件没有同时成功时,系统该如何继续工作?',
  125. },
  126. {
  127. slug: 'review',
  128. position: '课程收尾:逐一检查九个技术模块',
  129. responsibility:
  130. '用理解问题、情境推演和成果证据检验掌握程度,把薄弱点对应到原课时。',
  131. importance:
  132. '能跟着做出结果之后,还需要独立解释原因,并在条件变化时作出有依据的判断。',
  133. problem:
  134. '看答案觉得都懂,合上课件却讲不清、做不出,或不知道还需要补哪一课。',
  135. example:
  136. '继续用商品查询、编辑和清单导出,闭卷回答问题,核对要点,并记录需要回学的部分。',
  137. question: '学完之后,拿什么证明自己已经掌握?',
  138. },
  139. ];
  140. function HomeLink({
  141. module,
  142. navigate,
  143. children,
  144. className,
  145. }: {
  146. module: string;
  147. navigate: Navigate;
  148. children: ReactNode;
  149. className?: string;
  150. }) {
  151. return (
  152. <a
  153. href={courseHref(module)}
  154. className={className}
  155. onClick={(event: MouseEvent<HTMLAnchorElement>) => {
  156. if (
  157. event.button !== 0 ||
  158. event.metaKey ||
  159. event.ctrlKey ||
  160. event.shiftKey ||
  161. event.altKey
  162. )
  163. return;
  164. event.preventDefault();
  165. navigate(module);
  166. }}
  167. >
  168. {children}
  169. </a>
  170. );
  171. }
  172. function ArchitectureNode({
  173. title,
  174. lines,
  175. module,
  176. kind = 'service',
  177. }: {
  178. title: string;
  179. lines: string[];
  180. module?: string;
  181. kind?: 'service' | 'context' | 'queue' | 'storage' | 'runtime';
  182. }) {
  183. const className = `home-architecture-node home-node-${kind}`;
  184. const content = (
  185. <>
  186. <strong>{title}</strong>
  187. {lines.map((line) => (
  188. <span key={line}>{line}</span>
  189. ))}
  190. </>
  191. );
  192. return module ? (
  193. <a
  194. className={className}
  195. href={`#home-module-${module}`}
  196. aria-label={`查看${title}的课程定位`}
  197. >
  198. {content}
  199. </a>
  200. ) : (
  201. <div className={className}>{content}</div>
  202. );
  203. }
  204. function ArchitectureLayer({
  205. name,
  206. responsibility,
  207. kind,
  208. children,
  209. note,
  210. }: {
  211. name: string;
  212. responsibility: string;
  213. kind: string;
  214. children: ReactNode;
  215. note?: string;
  216. }) {
  217. return (
  218. <div className={`home-architecture-layer home-layer-${kind}`}>
  219. <div className="home-layer-heading">
  220. <h3>{name}</h3>
  221. <p>{responsibility}</p>
  222. </div>
  223. <div className="home-layer-content">
  224. <div className="home-layer-nodes">{children}</div>
  225. {note && <p className="home-layer-note">{note}</p>}
  226. </div>
  227. </div>
  228. );
  229. }
  230. function ArchitectureDiagram() {
  231. return (
  232. <div className="home-layered-architecture">
  233. <div className="home-architecture-stack">
  234. <ArchitectureLayer
  235. name="客户端层"
  236. responsibility="发起操作 · 接收结果"
  237. kind="client"
  238. >
  239. <ArchitectureNode
  240. title="客户端"
  241. lines={[
  242. '浏览器 / API 调用方',
  243. '提交请求、查询任务状态、订阅执行进度',
  244. ]}
  245. kind="context"
  246. />
  247. </ArchitectureLayer>
  248. <ArchitectureLayer
  249. name="接入层"
  250. responsibility="对外入口 · 流量转发"
  251. kind="access"
  252. >
  253. <ArchitectureNode
  254. title="07 入口网关 · Nginx / TLS"
  255. lines={['接收 HTTPS 请求,通过路由与反向代理转发到 API']}
  256. module="linux"
  257. />
  258. </ArchitectureLayer>
  259. <ArchitectureLayer
  260. name="业务服务层"
  261. responsibility="处理请求 · 组织业务"
  262. kind="business"
  263. note="短请求由 API 调用业务并返回;长任务在同一事务中保存任务与 outbox,提交后返回任务 ID。"
  264. >
  265. <ArchitectureNode
  266. title="01 API 服务"
  267. lines={['业务鉴权、参数校验', '同步响应 / 受理长任务']}
  268. module="api"
  269. />
  270. <ArchitectureNode
  271. title="Agent 编排*"
  272. lines={['规划步骤、管理上下文', '由 API 或 worker 调用']}
  273. kind="context"
  274. />
  275. <ArchitectureNode
  276. title="模型 / 工具*"
  277. lines={['由 Agent 编排调用', '生成、检索或执行动作']}
  278. kind="context"
  279. />
  280. </ArchitectureLayer>
  281. <ArchitectureLayer
  282. name="异步执行层"
  283. responsibility="交接任务 · 独立执行"
  284. kind="tasks"
  285. note="投递器读取已提交的 outbox;worker 幂等处理、写回 MySQL 后再确认消息,投递与执行均需考虑重试。"
  286. >
  287. <ArchitectureNode
  288. title="04 任务投递器"
  289. lines={['读取 outbox 并发送消息', '发送后更新投递状态']}
  290. module="tasks"
  291. kind="queue"
  292. />
  293. <ArrowRight
  294. className="home-task-arrow"
  295. size={18}
  296. aria-hidden="true"
  297. />
  298. <ArchitectureNode
  299. title="04 消息队列"
  300. lines={['Broker 暂存任务消息', '等待 worker 领取']}
  301. module="tasks"
  302. kind="queue"
  303. />
  304. <ArrowRight
  305. className="home-task-arrow"
  306. size={18}
  307. aria-hidden="true"
  308. />
  309. <ArchitectureNode
  310. title="04 Worker"
  311. lines={['执行工作 / 调用编排', '幂等处理、写回结果']}
  312. module="tasks"
  313. kind="queue"
  314. />
  315. </ArchitectureLayer>
  316. <ArchitectureLayer
  317. name="数据层"
  318. responsibility="访问数据 · 保存事实"
  319. kind="data"
  320. note="API 与 worker 按需访问;缓存未命中时读取 MySQL 真源。Redis 缓存与消息队列承担不同职责。"
  321. >
  322. <ArchitectureNode
  323. title="03 API + 数据库"
  324. lines={['管理会话与事务边界', '任务 + outbox 同事务提交']}
  325. module="api-database"
  326. kind="storage"
  327. />
  328. <ArchitectureNode
  329. title="02 MySQL"
  330. lines={['业务数据的持久真源', '商品、任务状态与结果']}
  331. module="database"
  332. kind="storage"
  333. />
  334. <ArchitectureNode
  335. title="05 Redis 缓存"
  336. lines={['保存可重建的查询结果', 'TTL 到期 / 更新时失效']}
  337. module="redis"
  338. kind="storage"
  339. />
  340. </ArchitectureLayer>
  341. <ArchitectureLayer
  342. name="运行基础层"
  343. responsibility="承载服务 · 部署恢复"
  344. kind="runtime"
  345. note="承载本地服务及依赖;外部模型与工具服务运行在各自系统中。"
  346. >
  347. <ArchitectureNode
  348. title="06 Docker"
  349. lines={['封装 API、worker 与本地依赖', '镜像、容器、网络与持久卷']}
  350. module="docker"
  351. kind="runtime"
  352. />
  353. <ArchitectureNode
  354. title="07 Linux"
  355. lines={['承载进程与容器', '权限、网络、发布与恢复']}
  356. module="linux"
  357. kind="runtime"
  358. />
  359. </ArchitectureLayer>
  360. </div>
  361. <aside className="home-crosscutting" aria-label="贯穿各层的能力">
  362. <div className="home-crosscutting-heading">
  363. <span>贯穿各层</span>
  364. <p>从请求入口到运行底座</p>
  365. </div>
  366. <a
  367. href="#home-module-troubleshooting"
  368. className="home-crosscutting-card"
  369. >
  370. <span className="home-crosscutting-number">08</span>
  371. <h3>观测与排障</h3>
  372. <p>用请求 ID / 任务 ID 串联各层证据。</p>
  373. <dl>
  374. <div>
  375. <dt>日志</dt>
  376. <dd>发生了什么</dd>
  377. </div>
  378. <div>
  379. <dt>指标</dt>
  380. <dd>整体运行怎样</dd>
  381. </div>
  382. <div>
  383. <dt>追踪</dt>
  384. <dd>慢在哪一段</dd>
  385. </div>
  386. </dl>
  387. </a>
  388. <a href="#home-module-distributed" className="home-crosscutting-card">
  389. <span className="home-crosscutting-number">09</span>
  390. <h3>分布式设计</h3>
  391. <p>审视跨进程、跨机器协作中的失败与取舍。</p>
  392. <ul>
  393. <li>超时与部分失败</li>
  394. <li>重复消息与幂等</li>
  395. <li>一致性与恢复</li>
  396. </ul>
  397. <span className="home-crosscutting-tag">设计视角 · 理论模块</span>
  398. </a>
  399. </aside>
  400. </div>
  401. );
  402. }
  403. const timeline = [
  404. [
  405. '提交请求',
  406. '商家点击“导出商品清单”。API 校验请求并确定商品快照;这时尚未承诺文件已经生成。',
  407. '当前请求',
  408. ],
  409. [
  410. '记录已提交',
  411. '在一个数据库事务里写入任务和待投递事件。事务提交后,API 返回 202 与任务 ID。',
  412. '受理 ≠ 完成',
  413. ],
  414. [
  415. '任务交接',
  416. '投递器读取事件并发送到队列。此时浏览器的原请求已经结束,投递失败可以稍后重试。',
  417. '异步开始',
  418. ],
  419. [
  420. '独立执行',
  421. 'worker 领取任务,把固定的商品快照写入导出文件;完成后保存结果引用,再确认消息。重复消息需要幂等处理。',
  422. '可能重试',
  423. ],
  424. [
  425. '读取结果',
  426. '客户端拿任务 ID 查询 API,或订阅进度。API 从任务记录判断等待、执行、成功或失败。',
  427. '看持久状态',
  428. ],
  429. ];
  430. export function CourseHome({ navigate }: { navigate: Navigate }) {
  431. const totalLessons = modules.reduce((sum, module) => sum + module.count, 0);
  432. return (
  433. <article className="course-home" id="course-home">
  434. <header className="home-hero">
  435. <div className="eyebrow">
  436. <span /> AGENT BACKEND · COURSE GUIDE
  437. </div>
  438. <h1 tabIndex={-1}>
  439. 先看见整个系统,
  440. <br />
  441. 再学会每一个模块<span className="title-dot">.</span>
  442. </h1>
  443. <p className="home-lead">
  444. 从一个容易理解的电商商品服务出发,逐步学会接住请求、保存商品、导出清单,并在失败时恢复。这些后端能力也构成服务端
  445. Agent 的基础。
  446. </p>
  447. <div className="home-actions">
  448. <HomeLink
  449. module="api"
  450. navigate={navigate}
  451. className="home-primary-link"
  452. >
  453. 从 API 第一模块开始 <ArrowRight size={17} />
  454. </HomeLink>
  455. <a href="#architecture" className="home-secondary-link">
  456. 先看系统全貌 <Workflow size={17} />
  457. </a>
  458. </div>
  459. <dl className="home-course-facts">
  460. <div>
  461. <dt>{String(modules.length).padStart(2, '0')}</dt>
  462. <dd>学习模块</dd>
  463. </div>
  464. <div>
  465. <dt>{totalLessons}</dt>
  466. <dd>课时与自测单元</dd>
  467. </div>
  468. <div>
  469. <dt>8 + 1 + 1</dt>
  470. <dd>后端实践 + 架构理论 + 复盘自测</dd>
  471. </div>
  472. </dl>
  473. </header>
  474. <aside className="home-scope-note" aria-label="课程范围">
  475. <BookOpen size={21} />
  476. <div>
  477. <strong>用简单的电商商品服务,走通整个后端链路。</strong>
  478. <p>
  479. 实战聚焦商品管理、分类、评价和清单导出;价格按分存储,上下架用布尔值表示。前
  480. 8 个模块依次补齐接口、存储、任务、缓存、部署和排障,第 9
  481. 个模块讲分布式设计理论,第 10
  482. 个模块逐一复盘并自测掌握程度。下图同时标出 Agent 的扩展位置;Agent
  483. 编排、模型与工具、完整业务鉴权用于说明所在位置,不属于本课已实现的完整
  484. Agent / RAG 项目。
  485. </p>
  486. </div>
  487. </aside>
  488. <section id="architecture" aria-labelledby="home-architecture-heading">
  489. <div className="home-section-heading">
  490. <span>01 / SYSTEM MAP</span>
  491. <h2 id="home-architecture-heading">把课程放进一张分层架构图</h2>
  492. <p>
  493. 从客户端到运行底座,按职责逐层理解系统;观测与设计能力贯穿各层。节点编号对应下方课程模块,点击可查看定位。
  494. </p>
  495. </div>
  496. <figure className="home-architecture-figure">
  497. <div className="home-diagram-legend">
  498. <span>
  499. <i className="home-legend-module" /> 课程覆盖模块
  500. </span>
  501. <span>
  502. <i className="home-legend-task" /> 异步任务交接
  503. </span>
  504. <span>
  505. <i className="home-legend-context" /> * Agent 扩展上下文
  506. </span>
  507. </div>
  508. <ArchitectureDiagram />
  509. <figcaption>
  510. 分层表示职责与支撑关系,请求按需访问相关组件,并非逐层依次执行。橙色箭头表示异步任务交接;API
  511. 负责业务鉴权,Nginx 负责入口代理。带 * 的 Agent
  512. 编排、模型与工具用于说明扩展位置,不属于本课已实现的完整系统。
  513. </figcaption>
  514. </figure>
  515. <div className="home-reading-paths">
  516. <article>
  517. <span className="home-reading-number">A</span>
  518. <h3>短请求:当场取回结果</h3>
  519. <p>
  520. 客户端 → 网关 → API → 数据库、缓存或业务调用 →
  521. 原路返回。用户等待的是这一次请求完成;慢依赖仍会占用请求的时间预算。
  522. </p>
  523. </article>
  524. <article>
  525. <span className="home-reading-number">B</span>
  526. <h3>长任务:先受理,再执行</h3>
  527. <p>
  528. API 写入任务和 outbox → 返回任务 ID。投递器 → 队列 → worker →
  529. 写回结果。用户之后再查询进度,接收成功并不等于执行成功。
  530. </p>
  531. </article>
  532. <article>
  533. <span className="home-reading-number">C</span>
  534. <h3>运行与可靠性:贯穿整张图</h3>
  535. <p>
  536. Docker 管运行单元,Linux
  537. 管主机与入口,观测帮助找故障。分布式设计关注它们之间的网络、重复、超时与不一致。
  538. </p>
  539. </article>
  540. </div>
  541. <p className="home-term-note">
  542. <strong>两个容易混淆的词:</strong>队列像“待办传送带”,worker
  543. 是实际做事的进程;Redis 可以用来缓存,也可以承担某些队列的 broker
  544. 角色,但两种用途的丢失容忍度、持久化和内存策略不同,不能因产品名相同就视为同一种数据。
  545. </p>
  546. </section>
  547. <section id="modules" aria-labelledby="home-modules-heading">
  548. <div className="home-section-heading">
  549. <span>02 / MODULE GUIDE</span>
  550. <h2 id="home-modules-heading">每个模块,补上哪一种能力</h2>
  551. <p>
  552. 前九个模块对应上图中的技术位置,最后一个模块用于复盘自测,检查这些能力能否独立运用。
  553. </p>
  554. </div>
  555. <div className="home-module-grid">
  556. {moduleDetails.map((detail, index) => {
  557. const catalogEntry = modules.find(
  558. (candidate) => candidate.slug === detail.slug,
  559. );
  560. if (!catalogEntry) return null;
  561. return (
  562. <article
  563. key={detail.slug}
  564. className="home-module-card"
  565. id={`home-module-${detail.slug}`}
  566. >
  567. <div className="home-module-card-top">
  568. <span className="home-module-number">
  569. {String(index + 1).padStart(2, '0')}
  570. </span>
  571. <span>
  572. {catalogEntry.count}{' '}
  573. {detail.slug === 'review' ? '自测单元' : '课时'} ·{' '}
  574. {detail.slug === 'distributed'
  575. ? '理论与面试'
  576. : catalogEntry.kicker}
  577. </span>
  578. <catalogEntry.icon size={19} />
  579. </div>
  580. <h3>{catalogEntry.name}</h3>
  581. <p className="home-module-question">{detail.question}</p>
  582. <dl>
  583. <div>
  584. <dt>所在位置</dt>
  585. <dd>{detail.position}</dd>
  586. </div>
  587. <div>
  588. <dt>负责什么</dt>
  589. <dd>{detail.responsibility}</dd>
  590. </div>
  591. <div>
  592. <dt>为何重要</dt>
  593. <dd>{detail.importance}</dd>
  594. </div>
  595. <div>
  596. <dt>解决问题</dt>
  597. <dd>{detail.problem}</dd>
  598. </div>
  599. <div>
  600. <dt>放进实例</dt>
  601. <dd>{detail.example}</dd>
  602. </div>
  603. </dl>
  604. <HomeLink
  605. module={detail.slug}
  606. navigate={navigate}
  607. className="home-module-entry"
  608. >
  609. 进入{catalogEntry.name}
  610. <ArrowRight size={16} />
  611. </HomeLink>
  612. </article>
  613. );
  614. })}
  615. </div>
  616. </section>
  617. <section id="journey" aria-labelledby="home-task-story-heading">
  618. <div className="home-section-heading">
  619. <span>03 / ONE LONG TASK</span>
  620. <h2 id="home-task-story-heading">把“导出一份商品清单”走一遍</h2>
  621. <p>
  622. 这是把多个模块连起来的设计示例:关键不在“后台”两个字,而在每个阶段承诺了什么、失败后由谁接手。
  623. </p>
  624. </div>
  625. <ol className="home-task-timeline">
  626. {timeline.map(([title, description, state], index) => (
  627. <li key={title}>
  628. <span className="home-timeline-index">{index + 1}</span>
  629. <div>
  630. <div className="home-timeline-title">
  631. <h3>{title}</h3>
  632. <span>{state}</span>
  633. </div>
  634. <p>{description}</p>
  635. </div>
  636. </li>
  637. ))}
  638. </ol>
  639. <div className="home-failure-note">
  640. <strong>如果 worker 刚写完结果就退出了?</strong>
  641. <p>
  642. 消息可能尚未确认,随后会被再次投递。所以“至少一次投递”仍需业务幂等:同一个任务
  643. ID
  644. 再次到达时,要识别已经提交的结果。若任务会向外部系统产生副作用,还需要对方支持幂等键或配套的核对与补偿流程。
  645. </p>
  646. <HomeLink module="distributed" navigate={navigate}>
  647. 去分布式模块推演更多失败场景 <ArrowRight size={15} />
  648. </HomeLink>
  649. </div>
  650. </section>
  651. <section id="learning-path" aria-labelledby="home-learning-path-heading">
  652. <div className="home-section-heading">
  653. <span>04 / LEARNING ROUTE</span>
  654. <h2 id="home-learning-path-heading">没有后端经验,就按这条路走</h2>
  655. <p>
  656. 会基础
  657. Python、能执行终端命令即可从第一模块开始。数据库、Docker、Linux
  658. 环境在对应模块准备,每一步都带着上一阶段的问题进入下一阶段。
  659. </p>
  660. </div>
  661. <ol className="home-learning-route">
  662. <li>
  663. <Route size={20} />
  664. <div>
  665. <h3>先让请求有入口,数据能保存</h3>
  666. <p>
  667. 01 API → 02 数据库 → 03 API +
  668. 数据库。先完成一个能正确提交、查询与修改数据的服务。
  669. </p>
  670. </div>
  671. <HomeLink module="api" navigate={navigate}>
  672. 开始 <ArrowRight size={16} />
  673. </HomeLink>
  674. </li>
  675. <li>
  676. <Workflow size={20} />
  677. <div>
  678. <h3>再处理等待、并发与重复读取</h3>
  679. <p>
  680. 04 异步任务 + 05 Redis。两者都建立在持久化 API
  681. 之上;任务模块进入队列部分前,先准备 Redis 基础环境。
  682. </p>
  683. </div>
  684. <HomeLink module="tasks" navigate={navigate}>
  685. 进入 <ArrowRight size={16} />
  686. </HomeLink>
  687. </li>
  688. <li>
  689. <BookOpen size={20} />
  690. <div>
  691. <h3>让服务运行起来,也能查清故障</h3>
  692. <p>
  693. 06 Docker → 07 Linux → 08
  694. 排障。用部署、停止、升级和恢复检验前面实现的边界。
  695. </p>
  696. </div>
  697. <HomeLink module="docker" navigate={navigate}>
  698. 进入 <ArrowRight size={16} />
  699. </HomeLink>
  700. </li>
  701. <li>
  702. <Workflow size={20} />
  703. <div>
  704. <h3>把组件连起来,解释架构为什么这样设计</h3>
  705. <p>
  706. 09
  707. 分布式系统设计。用已有组件经验讨论容量、数据与失败;围绕理论和面试场景展开,不安排实战代码。
  708. </p>
  709. </div>
  710. <HomeLink module="distributed" navigate={navigate}>
  711. 进入 <ArrowRight size={16} />
  712. </HomeLink>
  713. </li>
  714. <li>
  715. <BookOpen size={20} />
  716. <div>
  717. <h3>最后,检验每个模块是否真的掌握</h3>
  718. <p>
  719. 10
  720. 复盘与自测。每个技术模块对应一个自测单元:独立回答问题、推演变化、核对掌握证据,再按缺口回学。
  721. </p>
  722. </div>
  723. <HomeLink module="review" navigate={navigate}>
  724. 开始自测 <ArrowRight size={16} />
  725. </HomeLink>
  726. </li>
  727. </ol>
  728. </section>
  729. </article>
  730. );
  731. }