CHAPTER 07 / 自学试学版

把它交给别人:上线、观察、备份与恢复

读解释、检查证据、修订自己的设计。正文与参考记录可离线阅读,真实网络观察需要联网或启动本机服务。

导出包含第 0–7 章作答。导入会替换八章全部作答,包括文件中未包含的章节;建议先导出备份。

先修:接口、权限与可靠保存 · 三个短任务

7.1 “我这里能用”之后,还要核查什么?

小林把笔记应用发给朋友。首页能打开,自己的电脑也测试过,于是宣布上线成功。朋友却说保存不了。此前几章帮助我们设计正确流程;本章再问:发出的究竟是哪一版,它连接哪里,出问题后怎样找到证据,误改数据后怎样恢复?

把工作分成三个短任务:核对发布、沿请求观察、检查恢复。不要求搭监控平台、购买云资源或亲手部署后端。核心路线读记录并作判断;可选的本机实验只使用合成测试数据,不操作你的真实笔记。

完成后交出一张发布核对表、一条故障定位记录和一份恢复验收表。每项都写“证据支持什么、还缺什么”,不以页面打开或按钮变绿代替业务验证。

7.2 短任务一:版本、配置和业务路径要对应

代码版本回答“运行哪套逻辑”,配置回答“这套逻辑连接哪个地址、使用哪些开关”,数据状态回答“现在保存了什么”。三者相关但不是同一物。代码相同,接口地址配错,也可能让网页能开却无法保存;换了代码,数据库记录也不一定随之改变。

下面是教学情境:预期发布 V2,页面显示了新按钮,但保存地址仍指向 127.0.0.1。在小林电脑上,本机服务恰好运行;朋友打开时,这个地址却指向朋友自己的电脑。不是“换个浏览器就会好”,而是配置没有满足供他人访问的条件。

  1. 定位版本:记录公开网址、部署或提交标识,核对下载到的页面确实属于预期版本。只看标题相同不能辨认版本。
  2. 核对配置:检查业务接口地址、环境名称和功能开关。公开地址可写进前端;私密凭据不能藏在任何人都能下载的 HTML 中。
  3. 验证关键路径:在隔离测试环境用虚构数据完成创建、按返回编号读取,再核对内容;另测非法输入和越权拒绝。没有实现的能力明确记为未验证。

本课程的公开网站只提供静态课件,公开 GET 不能证明云端创建笔记。Python 演示的真实写入发生在本机;它也不因被打包下载就获得生产账号和权限能力。验收先核对系统声称提供什么,再检查相应路径。

判断一:首页返回 200,能否宣布笔记保存服务已验收?

下面的发布判断是教学模拟,不读取你或线上服务的实际运行状态。判断每条证据覆盖哪一层,再解释还应补什么。

7.3 短任务二:用少量记录选择下一步

朋友说“刚才保存失败”,单看总访问量帮不上忙。先缩小到一次操作:发生时间、页面版本、请求编号、路径、状态和耗时。请求编号串起同一次往返;第六章的操作编号则可能跨多次重试,两者不能混作一个计数单位。

教学例子,并非线上日志:
10:03  request=r17  POST /notes  status=422  elapsed=18ms
10:04  request=r18  POST /notes  status=503  elapsed=2040ms
10:05  request=r19  GET /notes   status=200  elapsed=24ms

对 r17,先对照第四章的输入契约和拒绝原因;对 r18,检查同一请求的应用错误、依赖与最近的配置变化。r19 读取成功,并不能说明 r18 创建了笔记。这里还没有写入记录,不能仅凭耗时长就断言“数据库坏了”。日志也不必保存笔记全文、密码或令牌;诊断通常先用编号和错误类别。

小规模运行先看业务保存成功与未确认的数量、端到端等待时间,以及可关联的失败记录。明确统计时间段、操作范围和分母;十次成功不能证明高峰也稳定。平均值可能藏住少量长等待,至少保留慢请求样本。指标告诉你哪里值得查,具体原因还要用请求与数据证据检验。

参考判断:下一步先做什么才不散?

先追 r18:确认哪个版本处理它、具体失败位置及数据库是否留下该操作;然后决定确认结果、修正配置或有限重试。不同先后也可接受,只要能排除明确候选解释。无需先搭一套大屏;当记录量使手动检索难以持续时,再考虑集中观测工具。

7.4 短任务三的前半:回滚程序,还是恢复数据?

新版本把保存按钮接错了,退回已验证的旧程序可能恢复功能;用户误删了笔记,换回旧网页通常不会让数据库里的记录回来。先定位受影响的对象,再选路径。还应保留失败版本、时间和错误证据,否则页面恢复后可能不知道问题是怎么发生的。

以现有静态课件托管为例,Cloudflare Pages 允许回滚到成功构建过的生产部署,预览部署不能直接作为回滚目标。这是更换生产部署,不是恢复学习者浏览器作答或外部数据库。回滚后仍需核对网址上的版本,并再走一遍受影响功能。官方回滚说明

应用若改过数据库结构,还要检查旧代码是否能读取当前结构;不能想当然地把旧程序指向新数据。核心学习只需指出这项兼容性条件,具体迁移和发布自动化留到自己的项目需要时再实现。

判断二:笔记被误删,但页面程序没有变化。最合适的第一步是什么?

7.5 恢复到新副本,逐项检查它带回了什么

“备份文件存在”只完成第一步。还要能打开、能还原,并知道还原的是哪个时点。下面记录由作者在临时 SQLite 数据库中使用合成笔记实际采集:保存测试记录 → 备份 → 删除测试数据 → 从备份恢复到另一份数据库 → 查询核对。这不是云上灾备演练,也不改动学习者的数据。

已执行的 Python / SQLite 记录 · 不是当前 HTTP 请求

采集时间(UTC):2026-10-04T22:34:20.017956+00:00;SQLite 3.42.0。仅用虚构数据与隔离临时库。

这里真正运行了数据库操作或应用函数。身份由测试程序提供,没有登录认证流程;状态码是函数返回的契约字段。原始前后快照供核对,不是发送给访问者的业务响应。

1. T0:创建一致备份,核对备份点
{
  "操作": "source.backup(backup);两个不同文件",
  "数据版本": "schema-v1",
  "备份点内容": [
    {
      "id": 1,
      "owner_id": "alice",
      "title": "晚餐菜单",
      "body": "番茄炒蛋",
      "version": 1
    },
    {
      "id": 2,
      "owner_id": "alice",
      "title": "周末清单",
      "body": "买两本书",
      "version": 1
    }
  ],
  "备份内容": [
    {
      "id": 1,
      "owner_id": "alice",
      "title": "晚餐菜单",
      "body": "番茄炒蛋",
      "version": 1
    },
    {
      "id": 2,
      "owner_id": "alice",
      "title": "周末清单",
      "body": "买两本书",
      "version": 1
    }
  ]
}
2. T1:备份之后新增一条
{
  "当前源库": [
    {
      "id": 1,
      "owner_id": "alice",
      "title": "晚餐菜单",
      "body": "番茄炒蛋",
      "version": 1
    },
    {
      "id": 2,
      "owner_id": "alice",
      "title": "周末清单",
      "body": "买两本书",
      "version": 1
    },
    {
      "id": 3,
      "owner_id": "alice",
      "title": "备份之后新增",
      "body": "这条尚未进入备份",
      "version": 1
    }
  ],
  "备份仍为": [
    {
      "id": 1,
      "owner_id": "alice",
      "title": "晚餐菜单",
      "body": "番茄炒蛋",
      "version": 1
    },
    {
      "id": 2,
      "owner_id": "alice",
      "title": "周末清单",
      "body": "买两本书",
      "version": 1
    }
  ]
}
3. T2:模拟误删,代码回滚没有执行任何数据恢复
{
  "当前源库": [],
  "说明": "数据库删除已提交;换HTML或应用代码版本不会自动把这些行写回来。"
}
4. T3:恢复到新副本并核查业务读取
{
  "操作": "backup.backup(restored-new-copy)",
  "目标": "新文件;没有覆盖源库",
  "恢复后内容": [
    {
      "id": 1,
      "owner_id": "alice",
      "title": "晚餐菜单",
      "body": "番茄炒蛋",
      "version": 1
    },
    {
      "id": 2,
      "owner_id": "alice",
      "title": "周末清单",
      "body": "买两本书",
      "version": 1
    }
  ],
  "条数": 2,
  "完整性检查": "ok",
  "业务读取": {
    "status": 200,
    "body": {
      "requestId": "req-001",
      "note": {
        "id": 1,
        "owner_id": "alice",
        "title": "晚餐菜单",
        "body": "番茄炒蛋",
        "version": 1
      }
    }
  },
  "源库仍为": [],
  "未恢复": "T1新增的编号3;没有比T0更新的备份或日志",
  "边界": "T0–T3是实验步骤名,不是测得的恢复耗时。未执行云灾备切流、断电、真实登录或性能测试。"
}
怎样复现与检查范围

下载底部实验包,运行 python3 evidence_lab.py。脚本在临时目录创建、检查和清理数据库;不会打开你的笔记库。运行结果只支持这些用例,不证明生产安全、并发压力、真实断电或云灾备。

记录对应源码 SHA-256:e7aecd720f9614777ee3d5c70996c518a381157d17f4988736b9061b7706481d。

CREATE TABLE users(id TEXT PRIMARY KEY);
CREATE TABLE notes(id INTEGER PRIMARY KEY, owner_id TEXT NOT NULL REFERENCES users(id),
 title TEXT NOT NULL CHECK(length(trim(title)) BETWEEN 1 AND 80),
 body TEXT NOT NULL DEFAULT '', version INTEGER NOT NULL DEFAULT 1 CHECK(version > 0));
CREATE TABLE tags(id INTEGER PRIMARY KEY, owner_id TEXT NOT NULL REFERENCES users(id),
 name TEXT NOT NULL, UNIQUE(owner_id,name));
CREATE TABLE note_tags(note_id INTEGER NOT NULL REFERENCES notes(id) ON DELETE CASCADE,
 tag_id INTEGER NOT NULL REFERENCES tags(id), PRIMARY KEY(note_id,tag_id));
CREATE TABLE shares(note_id INTEGER NOT NULL REFERENCES notes(id) ON DELETE CASCADE,
 reader_id TEXT NOT NULL REFERENCES users(id), PRIMARY KEY(note_id,reader_id));
INSERT INTO users VALUES ('alice'),('bob'),('eve');
INSERT INTO tags VALUES (1,'alice','学习'),(2,'bob','私有标签');
  1. 找到采集条件和备份时的记录,写下 ID、内容与条数,不能只记文件名。
  2. 确认删除后原库的状态;恢复目标须是新副本,不能把它与受损库混为一谈。
  3. 将新副本的记录逐项与备份点比较,再检查读取路径;若证据只展示数据库查询,不据此声称完整应用已经恢复服务。

本实验使用 SQLite 的备份能力。Python 的 Connection.backup() 将源连接的数据备份到目标连接;SQLite 官方提供的是一致快照机制,不是让你在数据库任意写入时随手复制文件。完成备份调用之后,仍要执行上面的恢复核验。Python 接口说明

本次实际记录把恢复点标成 T0:有两条笔记。T1 新增编号 3,T2 删除原库记录,T3 恢复的新副本只有 T0 的两条。它还调用应用读取函数核对正文,并确认编号 3 不存在;这些没有经过 HTTP 或真实登录。T0–T3 是步骤名,不是测得的耗时。

这说明备份之后的新增不会凭空回来。产品必须说清能退到哪里、可能损失哪些修改,以及允许恢复多久。更频繁备份能缩小时间缺口,但增加保存和管理成本;要承诺具体恢复时长,还需单独计时验证。

临时目录中的恢复成功证明这一次路径可执行,不证明磁盘丢失后仍有副本。真实项目还需安排与主数据独立的备份位置和定期恢复检查。切换前也要处理事故后仍在产生的新写入,决定暂停、保留或合并;不要为演练直接覆盖正在使用的数据。

7.6 换成家庭菜谱站,写一份能照着判断的运行说明

现在有两个故障:新版把收藏按钮接错;昨天的一道菜谱被误删。分别写出定位证据、处理路径和成功标准,并比较“先修新版本”与“先退回已验证版本”的代价。最后写一个让你改选方案的条件。

提示 1:先说要恢复哪个对象

是程序行为、运行配置、数据库记录,还是浏览器里的一份作答?对象不同,按钮相同也不能代表同一种恢复。

提示 2:每条路径补一个反例

回滚后仍连错接口怎么办?备份比被删菜谱更早怎么办?新副本条数正确但正文不同怎么办?让成功标准能发现这些问题。

参考判断与可接受替代

按钮故障可先确认版本与地址,再修复或回滚;前者保留新功能但需验证修复,后者恢复已知行为但可能暂失新功能。菜谱误删先验证合适备份中的 ID 与内容,再在新副本恢复。若有已验证的回收站或历史版本,也可选定向恢复,减少覆盖其他修改的风险;仍须检查范围和结果。

自评不等于已部署或已恢复真实服务。将本章说明加入持续设计档案,你已具备进入小项目设计的核心材料;性能和后台任务按实际需求再学。

精确选读