第一性原理拆解与架构全景


一、第一性原理:这个项目的本质是什么?

在动手之前,我先问自己 5 个 Why 把问题打到最底层:

Code
Q1: 我们要做什么? → 网络审计软件
Q2: 网络审计的本质是什么? → 被动观察 + 结构化记录 网络中的明文通信
Q3: "被动观察"的物理基础是什么? → 网卡混杂模式下逐帧捕获比特流
Q4: "结构化记录"的本质是什么? → 从无差别的字节流中,按协议分层规则提取语义字段
Q5: 那整个系统的最小抽象是什么? → 一个 **流式 ETL 管道**:
     Extract(抓帧) → Transform(逐层解码+语义提取) → Load(存储+推送)

打底结论:不管技术栈怎么选,这个项目的骨架就是一条 实时流处理管道(Streaming Pipeline),输入是原始以太网帧,输出是结构化审计记录 + 实时告警事件。


二、多层拆解:从字节到语义的四层认知

这个项目的核心难度不在"抓包",而在逐层剥壳——每一层协议都是上一层的 payload。我把它画成认知分层:

关键洞察:每一层的解析器只关心本层头部 + 判断下一层类型,然后把 payload 交给下一层。这天然就是 责任链模式(Chain of Responsibility) 的应用场景。


三、逆向验证:从评分标准反推架构约束

不能只正向设计,必须从评分标准逆向推导出架构的硬约束:

评分项分值反推出的架构约束
核心审计、抓包与存储60 分管道必须稳定:抓包不丢帧、解析不崩溃、存储不丢记录
实时告警 ≤ 10 秒10 分管道延迟必须 < 10s → 异步推送(SSE),不能轮询
每协议完整侦听每项 10 分协议解析器必须可插拔,每个独立开发独立测试
累计上限 1006 个协议全做满 = 60,加基础 60 已超 100,说明做全做稳就能满分

四、架构全景图:四大子系统

基于以上分析,整体架构切分为 四个子系统,它们之间通过 Kotlin Channel(协程通道)和 EventBus 解耦:


五、关键架构决策与 Why

这里是我做的每一个技术决策,以及为什么这样选(面向演讲答辩的"为什么"比"是什么"更重要):

决策 1:为什么用 Kotlin 协程而不是线程池?

Code
事实:抓包是 IO 密集型,每秒可能数千帧
问题:线程池在高并发 IO 场景下线程上下文切换开销大
选择:Kotlin 协程 = 用户态轻量级线程,10 万级并发无压力
验证:协程 Channel 天然提供背压(buffered channel 满了就挂起生产者)
     → 完美匹配 "不丢帧" 的硬约束

决策 2:为什么 Pcap4J 而不是直接 JNI 调 libpcap?

Code
事实:libpcap 是 C 库,JNI 调用有内存管理风险
问题:课程项目开发周期 4 周,没时间调 JNI 段错误
选择:Pcap4J = 纯 Java 封装 libpcap,API 友好,跨平台
验证:Pcap4J 已内置以太网帧 / IP / TCP / UDP 的解析类
     → 省掉 L2~L4 的手动解析,聚焦 L7 应用层解析(得分大头)

决策 3:为什么是 SSE 而不是 WebSocket?

Code
事实:告警是单向推送(服务端 → 浏览器),不需要客户端上行
问题:WebSocket 是双向的,对于单向场景是 over-engineering
选择:SSE = 基于 HTTP 的单向推送,原生浏览器支持,实现简单
验证:Ktor 原生支持 SSE,3 行代码搞定
     → 满足 ≤ 10 秒延迟的评分要求

决策 4:为什么 PostgreSQL 而不是 SQLite?

Code
事实:审计日志需要高速写入 + 复杂查询(按协议/时间/IP 过滤)
问题:SQLite 单写锁,并发写入会阻塞管道
选择:PostgreSQL 支持并发写入 + JSONB 字段存协议特有字段
验证:JSONB 让所有协议共用一张 audit_logs 表
     → 通用字段是列,协议特有字段进 JSONB,schema 简洁

决策 5:为什么协议解析器用策略模式 + 注册表?

Code
事实:6 个协议各自格式不同,但输入输出是统一的
问题:if-else 判断协议类型 → 违反开闭原则,加新协议要改老代码
选择:定义 ProtocolParser 接口,每个协议一个实现类,注册到 Map<Port, Parser>
验证:新增协议 = 新写一个类 + 注册一行
     → Codex 可以并行生成 6 个 Parser,互不干扰

六、数据流全景:一个 HTTP 请求从网卡到屏幕

用一个具体场景把整条管道串起来——这也是你答辩演讲时最有说服力的"Demo 故事线":


七、思维模型总结:本篇用了什么

思维工具用在哪里产出了什么
第一性原理§一识别出"流式 ETL 管道"这个最小抽象
分层拆解§二L2→L3→L4→L7 的认知地图,导出责任链模式
逆向推导§三从评分标准反推出 3 个硬约束(稳定/延迟/可插拔)
5 Whys§五 每个决策每个技术选型都有因果链,答辩时能自圆其说
具象化验证§六用一个 HTTP 请求走完全链路,验证管道设计无断裂

模块深潜 + 设计模式地图


一、设计模式全景地图:谁在哪里,为什么

先给一张全局鸟瞰——每个设计模式标注在它被使用的位置上,答辩时讲到任何一个模块都能说清"这里用了什么模式、为什么":

演讲技巧:答辩时指着这张图说"整个系统用了 10 个设计模式,每一个都有明确理由",瞬间拉开和普通项目的差距。


二、捕获引擎内部设计

2.1 逐层剥壳:责任链模式

核心思想:每一层解码器只负责拆自己那层的头部,判断下一层类型,然后把 payload 传下去。不认识的就丢弃。

为什么用责任链而不是一个大函数?

Code
正向理由:每层解码逻辑独立,单一职责,可独立单测
反向验证:如果都写在一个函数里——
  → 函数 200+ 行,改 IP 解析怕影响 TCP 解析
  → 违反开闭原则(加 IPv6 支持要改整个函数)
  → 不可测试(无法单测 IP 解码而不经过 Ethernet)

2.2 Pcap4J 的利用策略

关键认知:Pcap4J 已经内置了 L2~L4 的解析能力,我们不需要手动逐字节拆包

层次Pcap4J 提供的类我们只需做什么
L2 以太网EthernetPacket.getHeader().getType() 判断
L3 IPIpV4Packet.getHeader().getSrcAddr()
L4 TCPTcpPacket.getHeader().getDstPort() 路由
L4 UDPUdpPacket.getHeader().getDstPort() 路由
L7 应用层不提供这是我们写代码的主战场

结论:80% 的开发精力应该放在 L7 协议解析器上——这也是评分标准里"每协议 10 分"的得分点。


三、TCP 流重组:被低估的关键模块

3.1 为什么需要流重组?

Code
问题:一个 HTTP 请求可能跨越多个 TCP 段(分片)
      一个 TCP 段也可能包含多个 HTTP 请求(管线化)
事实:协议解析器需要的是「完整的应用层消息」,不是「单个 TCP 段」
结论:在分发给 L7 Parser 之前,必须有一层 TCP 流重组

3.2 流重组器设计

3.3 课程项目的简化策略

全功能 TCP 重组(如 libnids)极其复杂。课程项目可以做合理简化

全功能我们的简化理由
处理乱序包(按 SeqNum 排序)假设包按序到达LAN 环境 + 镜像端口,乱序极少
处理重传包(去重)简单 SeqNum 去重记录已见最大 SeqNum,跳过重复
支持半打开/半关闭连接超时清理即可设 60s 超时,避免内存泄漏
处理 TCP 窗口缩放忽略课程环境用不到

答辩话术:"我们做了工程权衡——在受控的 LAN 测试环境下,简化 TCP 重组是合理的;但架构上预留了扩展点,未来可以替换为完整实现。"


四、协议解析器深潜:六大 Parser 的内部设计

4.1 统一接口:策略模式

所有 Parser 共享同一个接口,输入是重组后的应用层消息 + 元数据,输出是结构化的 AuditRecord

Code
┌─────────────────────────────────────────────────┐
│              ProtocolParser 接口                  │
├─────────────────────────────────────────────────┤
│ + protocol: ProtocolType                         │
│ + ports: Set<Int>                                │
│ + parse(stream: StreamContext): AuditRecord?     │
│ + isComplete(buffer: ByteArray): Boolean         │
└─────────────────────────────────────────────────┘
          △         △         △         △
          │         │         │         │
    HttpParser  FtpParser  DnsParser  SmtpParser ...

为什么用接口而不是抽象类?

Code
接口:只定义契约,不强制继承链
     → HTTP 和 DNS 的解析逻辑完全不同(文本 vs 二进制),抽象类的公共方法会是空的
     → Kotlin 接口可以有默认实现,需要时仍可复用

4.2 HTTP Parser —— 最简单的入门 Parser

HTTP 是无状态的请求-响应模型,每条消息自包含,解析最直观:

解析要点

  • 请求行按第一个 CRLF 分割,空格切分得到 Method URL Version
  • Headers 按 CRLF 逐行,: 左右切 key-value
  • URL = 请求行的 URL(相对路径)+ Host 头拼接成完整 URL
  • isComplete 判断:看到 CRLFCRLF(空行)说明 Header 结束

4.3 FTP Parser —— 有状态的命令追踪

FTP 控制连接是逐行命令-响应,但需要跨多条命令追踪会话状态(用户名在 USER 命令,密码在 PASS 命令,操作在 RETR/STOR 命令):

状态机模式的价值

Code
没有状态机:收到 RETR file.txt 时,不知道是谁在操作
            → 得回溯之前的包找 USER 命令
            → 复杂、易错、性能差

有状态机:  StreamContext 里维护当前状态 + 已知 username
            → 收到 RETR 时直接组装完整 AuditRecord
            → O(1) 查找,无回溯

FTP 审计输出{ srcIP, dstIP, username, command: "RETR", filename: "secret.doc", directory: "/pub", timestamp }

4.4 TELNET Parser —— 逐字节的特殊挑战

TELNET 最特殊:数据是逐字节或逐小块发送的(用户每敲一个键发一个包)。

关键处理

  • IAC 过滤0xFF 开头的是 TELNET 控制指令(DO/DONT/WILL/WONT),不是用户数据,必须过滤掉
  • 回显识别:TELNET 服务端会回显用户输入,所以同一流的两个方向都有数据。我们只关心客户端→服务端方向(用户输入)
  • 用户名提取:监测服务端发送的 login: 提示,紧跟其后客户端发送的就是用户名

4.5 DNS Parser —— 唯一的二进制 + UDP 协议

DNS 是本项目里唯一不走 TCP 的协议(标准查询走 UDP 53),也是唯一的二进制格式

DNS 解析要点

  • 域名编码:DNS 域名用长度前缀编码(3www6google3com0),不是直接 ASCII 文本
  • 指针压缩:Answer 里的域名可能用 0xC0xx 指针回引 Question 的域名,节省空间
  • Query vs Response:通过 Header Flags 的 QR 位区分(0=查询,1=响应)
  • 关联策略:用 Transaction ID(Header 前 2B)关联请求和响应,提取 queryDomain + resolvedIP

4.6 SMTP Parser —— 最复杂的状态机

SMTP 的审计要求最丰富(发件人、收件人、标题、附件名与大小),状态转换也最复杂:

SMTP 解析的分层

层次解析内容难度
信封层MAIL FROM: / RCPT TO:★☆☆ 简单字符串提取
头部层Subject: / From: / To:★★☆ 可能多行折叠
MIME 层Content-Type: multipart / boundary / filename=★★★ 需要解析 MIME 结构
附件大小统计 boundary 之间的 Base64 长度 × 3/4★★☆ 近似计算即可

4.7 POP3 Parser —— SMTP 的镜像

POP3 与 SMTP 结构相似,但方向相反(收邮件而非发邮件):

Code
POP3 命令流:
  C: USER alice          → 记录用户名
  S: +OK
  C: PASS secret         → (可选记录)
  S: +OK
  C: RETR 1              → 开始接收邮件
  S: +OK ... (邮件内容)  → 解析邮件头: From/To/Subject/MIME 附件
  C: QUIT

复用思路:SMTP 和 POP3 的邮件头 + MIME 解析逻辑完全相同,可以抽成一个 EmailHeaderParser 工具类,两个 Parser 共同调用 → DRY 原则


五、事件总线:Observer + Fan-out

审计事件产生后,需要同时去两个地方:数据库 + SSE 推送。这是经典的 Observer 模式 + Fan-out:

为什么用 Kotlin SharedFlow 而不是自己写 EventBus?

Code
事实:Kotlin SharedFlow 是协程原生的多播热流
优势:
  1. 天然支持多个 collector(fan-out)
  2. 可配置 buffer 大小(背压控制)
  3. 协程安全,无需手动加锁
  4. replay=0 意味着只推新事件,不堆积历史
结论:零依赖,零 bug 风险,功能完全匹配

六、存储层设计:Schema + 批量写入

6.1 数据库 Schema —— 通用列 + JSONB 弹性字段

各协议的 JSONB details 内容示例

协议JSONB details 字段
HTTP{ method, url, host, user_agent, status_code }
FTP{ username, command, filename, directory, response_code }
TELNET{ username, command_line, direction }
DNS{ query_domain, query_type, resolved_ips[], is_response }
SMTP{ from, to[], subject, attachment_names[], attachment_sizes[], stage }
POP3{ username, from, to[], subject, attachment_names[], mail_size }

为什么一张表 + JSONB 而不是每个协议一张表?

Code
方案A(6 张表):HttpLog, FtpLog, TelnetLog ...
  → 全协议联合查询需要 UNION 6 张表 → 慢、代码复杂
  → 前端 "全部审计记录" 页面要拼 6 个接口

方案B(1 张表 + JSONB):
  → 全协议查询 = 一个 SELECT → 简单
  → 按协议过滤 = WHERE protocol = 'HTTP' → 一样快
  → 协议特有字段进 JSONB,GIN 索引支持 JSON 路径查询
  → schema 变更零成本(加新字段不用 ALTER TABLE)

6.2 批量写入器 —— Buffer + 定时刷盘

逐条 INSERT 太慢,攒一批再 bulk insert:

双触发条件的原因

  • 只用数量:低流量时日志要等很久才写入 → 告警延迟超 10s
  • 只用时间:高流量时 2s 内可能几千条,内存爆
  • 两个条件取 OR:兼顾延迟吞吐

七、前端组件树与数据流

7.1 页面结构

7.2 前端实时数据流(SSE → Pinia → 组件)

为什么用 Pinia 而不是组件内状态?

Code
问题:SSE 数据需要同时驱动 4+ 个组件
方案A(组件 prop 传递):根组件接 SSE → 层层传 → prop drilling 地狱
方案B(Pinia 全局状态):SSE → Store → 任意组件 inject → 扁平
结论:Pinia 是 Vue 3 官方状态管理,SSE 写 Store,组件自取所需

八、告警引擎:规则模式

评分要求"告警延迟 ≤ 10 秒"——我们的管道本身就是亚秒级的,但加一个可配置的告警规则引擎能极大提升演示效果:

演示亮点:答辩时现场触发一个 TELNET 登录 → 大屏立刻弹出红色告警卡片 + 声效 → 评委印象深刻。


九、设计模式汇总 Cheat Sheet

#模式在哪用解决什么问题答辩一句话
1责任链L2→L3→L4 解码链逐层解耦,可独立扩展"每层只管自己的头部"
2策略ProtocolParser 接口6 种协议统一接口、可插拔"新协议 = 新类 + 注册一行"
3状态机FTP/SMTP/POP3 会话跨包追踪会话状态"USER 和 RETR 隔了 N 个包,状态机帮我记住"
4观察者SharedFlow 事件总线一个事件,多个消费者"解析完发事件,DB 和 SSE 各取所需"
5BuilderAuditRecord 构建字段来自不同解析阶段"IP 来自 L3,端口来自 L4,URL 来自 L7,Builder 拼起来"
6RepositoryAuditRepository隔离 DB 细节"换 DB 只改 Repository 实现"
7RegistryParserRegistry端口→解析器的映射"查表分发,O(1) 路由"
8Adapter前端展示层不同协议统一渲染"JSONB details 适配成统一的表格列"
9Batch/Buffer批量写入器吞吐 vs 延迟平衡"攒 200 条或等 2 秒,哪个先到就刷盘"
10Template Method捕获引擎生命周期统一 open→loop→close"子类只覆盖 processPacket"

十、Kotlin 密封类的架构价值

最后一个重要设计点:用 Kotlin sealed class/interface 建模协议类型和审计事件——这让编译器替我们检查有没有漏处理某个协议:

Code
密封类优势演示:

sealed interface AuditEvent {
    data class HttpEvent(...) : AuditEvent
    data class FtpEvent(...) : AuditEvent
    data class TelnetEvent(...) : AuditEvent
    data class DnsEvent(...) : AuditEvent
    data class SmtpEvent(...) : AuditEvent
    data class Pop3Event(...) : AuditEvent
}

when (event) {
    is HttpEvent -> ...
    is FtpEvent -> ...
    // 如果漏了某个 → 编译器直接报错 ❌
    // 比 if-else + String type 安全 100 倍
}

答辩话术:"我们用 Kotlin 的密封类让类型系统替我们做完整性校验——每加一个新协议,所有 when 表达式都会编译报错,逼着你把逻辑补全。这比运行时抛异常靠谱得多。"


实施路线 + 演示策略 + 冲分点


一、4 周 Sprint 路线图:倒排法

第 9 周周日交付倒推,预留最后 1 周整理成果,实际开发窗口是 4 周。用逆向规划确保不漏关键路径:

Sprint 关键里程碑(验收标准)

Sprint里程碑自检方式
S1 结束能抓到包并在控制台打印出 srcIP:srcPort → dstIP:dstPorttcpdump 对照验证
S2 结束HTTP/FTP/DNS/TELNET 四个协议的审计记录输出到日志文件curl + ftp + nslookup + telnet 手动触发
S3 结束所有 6 个协议 → PostgreSQL 写入成功 + SSE 能推出事件浏览器 EventSource 控制台看到 JSON
S4 结束Dashboard 完整展示 + 实时刷新 + 告警弹窗全链路 Demo 跑通

二、Codex 任务拆分策略:让 AI 高效并行

核心原则:每个 Codex 任务必须自包含——有明确输入、输出、接口约定,不依赖其他未完成的任务。

Codex 每个 Task 的 Prompt 结构(统一模板思维)

Code
每个 Task 提交给 Codex 时,提示词结构应包含:

1. 【角色】你是 Kotlin 网络安全开发者
2. 【上下文】给出接口定义 + 数据模型(从 Task 2 产出)
3. 【输入】明确入参类型(如 StreamContext)
4. 【输出】明确返回类型(如 AuditEvent.HttpEvent)
5. 【约束】错误处理、日志规范、不要阻塞协程
6. 【测试】附带单元测试用例的输入输出对

关键洞察:Wave 3 的 6 个 Parser 是完全并行的——它们共享同一个 ProtocolParser 接口,输入输出已在 Wave 1 的 Task 2 中定义好。可以同时提交 6 个 Codex 任务,极大压缩开发时间。


三、测试环境搭建:可复现的受控流量

3.1 整体测试架构

3.2 一键触发全协议测试脚本

设计一个 shell 脚本,一键触发 6 种协议流量,答辩时直接运行:

Code
test-all-protocols.sh 的逻辑流:

1. curl http://nginx/index.html              → 触发 HTTP 审计
2. curl http://nginx/admin/secret.html        → 触发 HTTP + 告警(敏感 URL)
3. ftp -n ftpserver <<< "USER alice; PASS 123; RETR secret.doc; QUIT"
                                               → 触发 FTP 审计
4. echo "ls -la" | telnet telnetserver         → 触发 TELNET 审计
5. nslookup example.com dnsserver              → 触发 DNS 审计
6. swaks --to bob@test.com --from alice@test.com \
         --server smtpserver --attach file.pdf → 触发 SMTP + 附件审计
7. fetchmail --protocol POP3 popserver          → 触发 POP3 审计

答辩演示:运行脚本 → 切到 Dashboard → 看到 6 种颜色的审计记录实时涌入 → 红色告警弹窗弹出 → 全场惊艳。

3.3 用 Docker Network 模拟镜像端口

Code
问题:开发环境没有真实的交换机镜像端口
方案:Docker bridge 网络 + 容器内混杂模式
原理:
  1. 所有容器在同一个 Docker bridge 网段
  2. 审计系统容器用 --net=host 或 --cap-add=NET_RAW
  3. Pcap4J 在 bridge 接口上抓包 = 等效于镜像端口
验证:tcpdump -i docker0 可以看到所有容器间流量

四、答辩演示脚本:讲一个故事

演示原则:不展示功能列表,而是讲一个安全事件的故事——让评委跟着你的叙事线,自然看到所有功能。

4.1 演示故事线(8 分钟脚本)

4.2 每个场景的详细演示动作

场景 1:HTTP 审计(正常行为)

Code
演示动作:
  1. 打开 Dashboard → 当前审计记录为空
  2. 在终端执行 curl http://webserver/index.html
  3. 切到 Dashboard → 表格实时出现一行 HTTP 记录
     → 展示 srcIP, dstIP, URL, Method, Timestamp
  4. 再执行 curl http://webserver/api/users
  5. Dashboard → 第二行出现 → 饼图 HTTP 占比变化 → 折线图跳动

讲解话术:
  "每一个 HTTP 请求,从网卡抓包到 Dashboard 展示,
   延迟不到 1 秒。这背后是 Kotlin 协程 Channel 管道 +
   SSE 实时推送。"

场景 2:SMTP 邮件审计(数据丰富)

Code
演示动作:
  1. 用 swaks 发送一封带附件的邮件
  2. Dashboard → SMTP 记录出现
     → 点击展开:发件人、收件人、标题、附件名、附件大小
  3. 强调:"附件大小是通过 MIME boundary 间 Base64 长度计算的"

讲解话术:
  "SMTP 解析是本项目最复杂的模块——它是一个 8 状态的
   状态机,从 EHLO 到 DATA 到 MIME 解析,逐层深入。"

场景 3:TELNET 入侵告警(高潮)

Code
演示动作:
  1. 在终端 telnet 到服务器
  2. 输入 login: hacker → 输入 password → 执行 cat /etc/passwd
  3. Dashboard → TELNET 记录出现 + 红色告警弹窗弹出!
     → 弹窗内容: "TELNET 登录检测: hacker @ 192.168.1.100"
     → 告警声效播放(可选)
  4. 点击告警 → 跳转到该条审计详情

讲解话术:
  "告警引擎在事件产生的同一毫秒内进行规则匹配,
   SSE 推送到前端弹窗,端到端延迟 < 1 秒,
   远低于评分要求的 10 秒。"

五、冲分策略:从 60 到 100 的加分点地图

五大差异化加分点详解

#加分项实现难度演示效果投入产出比做法
1GeoIP 地图★★☆★★★★★极高Leaflet + MaxMind GeoLite2 离线库,dst_ip → 经纬度 → 地图打点
2协议饼图★☆☆★★★★极高ECharts pie,Pinia 实时计数 → watchEffect 驱动刷新
3流量折线图★★☆★★★★ECharts line,每秒聚合一个点,滚动窗口 60 点
4设计模式展示★☆☆★★★PPT 里放 Reply 2 的模式地图,答辩逐个讲
5Docker 一键部署★★☆★★★docker-compose up 启动全栈(含测试服务端 + 审计系统 + DB)

策略:加分项 1~3 是视觉冲击力最高的,评委看到地图上实时打点、饼图实时旋转、折线图实时跳动,会有"这远超课程项目水平"的感觉。


六、PDCA 迭代闭环:每日自检

Check 环节的三层验证

pcap 回放测试的价值

Code
问题:开发时不可能每次都真实发送 6 种协议的流量
方案:
  1. 用 tcpdump -w test.pcap 录制一次完整的测试流量
  2. 开发时用 Pcap4J 的 PcapHandle.openOffline("test.pcap") 回放
  3. 结果与 Wireshark 的解析结果逐字段对比
优势:
  → 可重复:同一个 pcap 文件跑 100 次结果一致
  → 可回归:改了 HTTP Parser 不怕影响 FTP Parser
  → 可 CI:放到自动化测试里

七、风险预案:Murphy 定律防御

风险概率影响预案
Pcap4J 在某些 OS 上权限问题Docker --cap-add=NET_RAW + --net=host;备选方案:读 pcap 文件模式
SMTP/POP3 MIME 解析太复杂先实现信封层(MAIL FROM/RCPT TO)拿 80% 分,MIME 附件是锦上添花
TCP 流重组丢包导致解析失败加 try-catch 容错 + 日志记录 → 丢一条不影响管道 → 答辩时说"我们做了优雅降级"
Docker Compose 环境配置耗时先用本地 nc/curl 手动测试,Docker 作为最后的锦上添花
前端 SSE 断连EventSource 内置自动重连;Pinia Store 里加 connectionStatus 状态指示

八、成果交付清单

录制 Demo 视频的必要性

Code
为什么要录屏?
  → Murphy 定律:答辩现场网络可能出问题
  → 备选方案:如果 live demo 失败,立刻切到录屏
  → 专业感:提前录好的视频可以加字幕、高亮关键区域
  → 建议:用 OBS 录制 1080p,3~5 分钟精华版

九、答辩 PPT 结构建议(10 页)

PPT 核心原则:每一页只有一张图 + 一句话。文字墙是答辩大忌。Reply 1&2 中的 Mermaid 图可以直接导出到 PPT。


十、三篇回复的思维模型闭环

最后,回顾整个 3 篇回复的思维链条,形成完整闭环: