🧪 第4章 应用程序的规格与准备
📄 一句话概括

这一章不写业务代码,只把六张页面的地址与字段定下来。 字段表是数据库表的上游:先定页面要显示什么,再定表要存什么。 错误页也算一张页面。它不占地址,但不写的话用户看到的是英文默认页。

📝 本章和前面几章的关系

这一篇对应教材 4.1 到 4.1.6:规格、图片列表、侦测、上传、物件侦测、搜寻、自订错误页。 这是全书唯一一章不产生新代码的,但它同时是后面八章的地图——写代码卡住时回来看它。

一、为什么不直接开写

前面的章节每一步都有明确目标:搭骨架、接数据库、加登录。 到这里忽然要做一个「真应用」,第一反应往往是先建模型再说。 教材的选择是先写规格:六张页面、每张要显示什么、走什么方法。一页纸写完,再动手。 理由很实在:字段改起来,越晚越贵。库建好了再改字段要写迁移,模板写好了再改字段要重排页面, 两样都比在纸上改一个字贵。

所以这一章看着像「废话」,实际是在省后面的返工。

二、六张页面与它们的地址

页面 地址 方法
图片列表 /images/ GET
图片详情 /images/ GET
上传图片 /images/upload GET 与 POST
执行侦测 /images//detect POST
图片搜寻 /images/search GET
错误页 (不占地址) —

地址里那个 <int:iid> 是路由变量,第 1 章讲过。 用 int 转换器有个好处:/images/abc 会直接 404,不会进到视图里再报类型错。 「执行侦测」挂 POST,因为它会改数据(写入侦测结果)。这条规矩前面已经用过两次:删除、登出。 上传页一个地址两种方法:GET 给表单,POST 收文件。这是第 2 章 2.6 那个写法的直接复用。

三、字段表:数据库的上游

教材给每张页面列了要显示的字段。六页加起来 23 个字段,平均每页不到 4 个。 看着琐碎,但它回答了一个关键问题:这张表到底要存什么。页面上要显示的,库里必须有; 库里有的,不一定每页都显示。 比如「侦测件数」在列表页和详情页都要显示。 它有两种存法:存成 UserImage 的一个列,或者每次去数结果表。

存列快但会不一致,去数慢但永远是对的。教材后面选了后者——第 5 章的关联查询就是为它准备的。 这类取舍在写代码之前想清楚,比写完再改省事得多。

四、错误页也是一张页面

4.1.6 单独列了自订错误页。它不占地址,但用户一定会撞上——链接点错、记录被删、手打地址打错。 实测:没挂处理器时,404 返回的是 207 字节的英文默认页; 自己写一个,76 字节,至少告诉用户该点哪儿回去。 关键一条:状态码必须是 404,不能改成 200。改成 200 的话, 搜索引擎会把一堆错误页当正常页面收录。

顺手记一个坑:这个处理器只能挂在应用上。 在蓝图里写 @bp.errorhandler(404) 实测不生效,因为 404 是「没有任何规则匹配」, 那一刻 Flask 还没确定该归哪个蓝图。

五、规格写完之后怎么用

把那张地址表当清单:每写完一个端点就划掉一行。八个章节之后,六行全划完,应用也就做完了。 把那张字段表当检查表:写模型时对着它一列一列过,不容易漏。 规格最大的价值不是「预先设计」,是「随时能核对」——写到一半发现页面缺点东西,回来补一行, 比写到结尾再推翻重来便宜。

六、动手 Demo:六个页面的规格:字段、地址与错误页

这个 Demo 把那张规格表变成能跑的东西:六条地址先立成空壳,逐个访问看返回码。 最后一节对比默认 404 与自订 404 的体量。

PAGES = [
    ("图片列表", "/images/", "GET", [...]),
    ("图片详情", "/images/<int:iid>", "GET", [...]),
    ("上传图片", "/images/upload", "GET 与 POST", [...]),
    ("执行侦测", "/images/<int:iid>/detect", "POST", [...]),
    ("图片搜寻", "/images/search", "GET", [...]),
]

@app.errorhandler(404)
def not_found(e):
    return render_template("404.html"), 404

实跑输出:

--------------------------------------------------------------------
六个页面的规格:字段、地址与错误页
--------------------------------------------------------------------

  ① 六张页面各自的地址
  --------------------------------------------------------------------

| 页面 | 地址 | 方法 |
|---|---|---|
| 图片列表 | /images/ | GET |
| 图片详情 | /images/<int:iid> | GET |
| 上传图片 | /images/upload | GET 与 POST |
| 执行侦测 | /images/<int:iid>/detect | POST |
| 图片搜寻 | /images/search | GET |
| 错误页 | (不占地址) | — |

  ! 先定地址再写代码,好处是「谁调谁」在纸上就定了。
  第 5 章以后每加一个功能,都是往这张表里补一行。

  ② 每张页面要显示什么字段
  --------------------------------------------------------------------

| 页面 | 字段 |
|---|---|
| 图片列表 | id、文件名、缩略图、侦测件数、上传时间、操作 |
| 图片详情 | id、文件名、原图、侦测框、侦测件数、上传者 |
| 上传图片 | 图片文件、备注 |
| 执行侦测 | id、模型名、阈值 |
| 图片搜寻 | 关键字、起日、讫日 |
| 错误页 | 状态码、说明、回首页链接 |

  六张页面一共 23 个字段,平均每页 3.8 个
  ! 这张表是「数据库要存什么」的上游。
  字段一旦定死,模型和迁移就照着它写——反过来先写模型再想页面,一定会漏字段。

  ③ 地址能不能都立起来
  --------------------------------------------------------------------
  /images/               -> 200
  /images/7              -> 200
  /images/upload         -> 200
  /images/7/detect       -> 200
  /images/search         -> 200
  /images/nope           -> 404
  ! /images/nope 是 404,因为它在规格里根本不存在。
  规格的用处之一就是:不该通的地址,通不了是对的。

  ④ 自订错误页顶掉默认那一页
  --------------------------------------------------------------------
  没挂错误处理器时,404 页面 207 字节
  挂上之后,同样一个地址 76 字节
  自定义页面的内容 -> <h1>找不到这一页</h1><p>地址可能打错了,回首页看看。</p>
  ! 默认那一页是英文的、只有一行。换成自己的,至少能让用户知道该点哪儿回去。
  ! 实测这一收一发只改了 404 的响应体,状态码还是 404——不能改成 200,
  否则搜索引擎会把错误页当成正常页面收录。
  ! 这一节要建两个应用实例,是因为 Flask 2.3 之后禁止在「处理过第一个请求」之后再注册错误处理器。
  同一件事在真实项目里不会遇到——错误页总是在 create_app 里一次注册好。

第三节那六行里,只有 /images/nope 是 404——它在规格里本来就不该存在。 第四节默认页 207 字节、自订页 76 字节,差别不在长短,在「看不看得懂」。 那个报错式的注记是实情:Flask 2.3 之后不允许在处理过请求之后再注册错误处理器, 所以这一节建了两个应用实例。

30 秒实验

  1. 给规格表再加一张「侦测结果详情」页面,想想它需要哪些字段、地址怎么排。
  2. 把错误页的状态码改成 200,用浏览器的网络面板看它变成什么颜色。
  3. 想一想:为什么「侦测件数」存成列会有不一致的风险?

常见误区

  1. 先建模型再想页面:写到第三张页面才发现少一个字段,改模型要迁移、改模板要重排。
  2. 错误页返回 200:搜索引擎会把错误页收进索引,用户搜到的全是空页面。
  3. 侦测这种改数据的操作用 GET:挂 POST 的理由和删除、登出完全一样。
  4. 在蓝图里注册 404:不生效,而且不报错,表现为「我写的错误页怎么不出来」。
自测三问(点开对答案) 1. 为什么这一章只写规格不写代码? —— 字段改得越晚越贵,先在纸上定死能省后面的返工。 2. 「侦测件数」存列与现数各有什么代价? —— 存列快但可能不一致,现数慢但永远是对的。 3. 自订错误页为什么要保持 404? —— 改成 200 会被搜索引擎当正常页面收录。

规格定了,下一篇按它搭目录:images 包、蓝图注册、第一版列表页。

相关笔记