码上问学问代码之理,学编程之道

阶段 1 · 需求与设计

项目的第一件事不是 CREATE DATABASE,而是把「要做什么」写清楚。这一阶段不写一行业务代码,却决定后面六个阶段的返工量:需求不清,表白建;接口没定,前端只能干等;表结构改了,后端跟着塌。学完这一阶段,你要交出一份《需求与设计说明书》——里面有角色、功能清单、原型草图、ER 图、接口约定和项目计划。

为什么工程要先设计

先想清楚,再动手
先讲为什么装修房子时,没人会「先砌墙,砌完再想厨房放哪儿」。可到了写代码,很多人就是这么干的:打开编辑器就建表、写接口,写到一半才发现「作品是要分分类的」,于是回头改表、改接口、改前端——三处同时返工。
设计就是那张施工图,它本身不产生任何功能,但它让后面每一步只走一遍。经验值很直白:需求阶段改一处花 1 分钟,上线后再改同一处要花 100 分钟。
讲透:这一阶段的「产出」是六份能拿去评审的东西
  • 角色与权限——谁能做什么、谁不能做什么。
  • 功能清单——模块 / 功能点 / 优先级,条目要能和接口一一对上。
  • 原型草图——页面长什么样,信息先摆哪儿。
  • 数据库设计——有哪些表、谁指向谁。
  • 接口约定——请求长什么样、响应长什么样。
  • 项目计划——七个阶段各做什么、各交什么。
它们合起来就是《需求与设计说明书》。注意顺序:先定「谁用、要什么」,再定「数据怎么存」,最后才是「接口怎么调」——反过来做,接口会跟着表结构反复推翻。
避坑两个极端都要避开:① 完全不设计,边写边改,代码里到处是「先这样,以后再说」;② 过度设计,上来就讨论「用户表要不要拆成三张表」「以后支持多语言怎么办」,把三天能开工的事拖成两周。本课程的标准只有一句话:只设计到「能开工」为止——够用、写清、可验收,就停。

需求分析:谁在用、要什么

三个角色 · 一份功能清单
先讲为什么需求分析的第一步不是列功能,而是列人。同一句「能删评论」,游客、作者本人、管理员的操作权限完全不同:游客连按钮都不该看见,作者只能删自己的,管理员谁都能删。
先分清角色,功能清单才不会写成一锅粥。
动手做三个角色写进文档时,要写清「能做什么」和「不能做什么」两列——只写能做什么,评审时一定会有人问「那他能删别人评论吗」。
需求与设计说明书.mdmarkdown
## 二、用户角色与权限

| 角色 | 可以做什么 | 不可以做什么 |
|---|---|---|
| 游客 | 浏览首页 / 作品列表 / 作品详情 / 书评列表;按分类、朝代筛选;按标题搜索 | 发表书评、点赞、收藏、修改资料 |
| 注册用户 | 游客的全部能力,另加:发表并删除自己的书评、点赞作品与书评、收藏与取消收藏、修改资料与上传头像 | 维护分类与轮播图;删除他人的书评 |
| 管理员 | 全部能力,另加:分类与轮播图维护、违规书评下架、用户禁用 | —— |
效果预览运行结果
运行结果: $ ls -1 docs/ 需求与设计说明书.md

角色摊开之后,再列功能清单。每一条都要写清三件事:模块、功能点、优先级。优先级用 P0 / P1 / P2 标——P0 是不做就没法验收,P1 是本周期要做,P2 是有空再说。

模块 功能点 优先级 对应接口
用户注册(用户名唯一、密码 6–20 位)P0POST /api/user/register
用户登录(签发 token)P0POST /api/user/login
用户查看个人信息(含书评数、收藏数)P0GET /api/user/profile
用户修改个人信息(昵称 / 简介 / 性别)P1PUT /api/user/profile
用户上传头像(jpg / png / gif,≤ 2 MB)P1POST /api/user/avatar
用户退出登录(前端清 token,后端记日志)P2POST /api/user/logout
首页轮播图列表(按 sort 排)P0GET /api/banner/list
首页分类列表(筛选下拉框的数据源)P0GET /api/category/list
首页文学推荐(推荐位,按浏览量倒序)P0GET /api/article/recommend
作品作品列表(分页 / 分类 / 朝代 / 搜索 / 排序)P0GET /api/article/list
作品作品详情(浏览量 +1,带收藏 / 点赞状态)P0GET /api/article/detail/<id>
作品点赞作品(重复点赞 = 取消)P1POST /api/article/like
书评书评列表(楼中楼:回复挂在父书评下)P0GET /api/comment/list
书评发表书评(支持回复某条书评)P0POST /api/comment/add
书评点赞书评P1POST /api/comment/like
书评删除自己的书评(软删除,越权返回 403)P1DELETE /api/comment/<id>
收藏收藏 / 取消收藏(同一个接口来回切)P1POST /api/favorite/toggle
收藏我的收藏(分页)P1GET /api/favorite/list
讲透:功能清单是三份东西的同一个源头
  • 它同时是接口清单和验收清单。上面 18 条功能点,正好对应 18 个接口(见 API 接口文档);验收测试也是逐条打勾。
  • 优先级不是排序,是「砍需求的顺序」。时间不够时先砍 P2,再砍 P1;P0 一条都不能砍,否则项目不算完成。
  • 颗粒度要一致:「注册」是一条功能点,不要写成「注册 + 校验 + 加密 + 返回 token」四条——那是实现步骤,不是功能。
避坑别把「优化」「美化」「响应式」写成功能点——它们没有明确的完成标准,写进 P0 就永远做不完。它们属于下一节的非功能需求,而且必须写成可量化的指标。

非功能需求:性能、安全、兼容

写不出指标的,验收时一定吵架
先讲为什么功能需求回答「能做什么」,非功能需求回答「做得多好」。它们最容易在验收时扯皮:你说「挺快的」,老师说「我觉得慢」——因为没有指标。
把指标先写进文档,验收就从「争论」变成「对照」。
类别 指标 本项目目标 怎么验证
性能接口响应时间单表查询接口 P95 < 500 ms(本地开发环境)Postman 看响应耗时
性能分页保护page_size 上限 50,传 99999 会被夹取?page_size=99999 看返回条数
性能并发支持 50 人同时浏览不报错阶段 5 压测一遍
安全密码只存哈希(Werkzeug scrypt),响应里永不出现 password查响应字段 + 查库表
安全鉴权需登录接口必须带 Bearer token;越权删除他人书评返回 403不带 token / 换个账号试
安全注入全部走 ORM 或参数化查询,不拼 SQL 字符串代码评审,搜字符串拼接
安全上传只允许 jpg / png / gif,单文件 ≤ 2 MB,文件名用 uuid 重命名传一个 .php 试试
兼容浏览器Chrome / Edge / Firefox 最新两个大版本阶段 5 跨浏览器测试
兼容分辨率1280 px 以上完整显示,375 px 可用不溢出拖动窗口宽度观察
兼容运行环境Python 3.10+ / Flask 3.x / MySQL 8.x / Node 18 LTS下方这条命令的输出
动手做兼容性的第一条是「跑在什么版本上」。先把环境基线记进文档——后面阶段 2、3、6 的命令全都以它为前提。
确认环境基线.shbash可运行
python --version
mysql --version
node -v
npm -v
终端输出命令行执行结果
运行结果: Python 3.10.11 mysql Ver 8.0.36 for macos14 on arm64 (MySQL Community Server - GPL) v18.20.4 10.7.0
讲透:非功能需求往后每一步都在约束你的代码
  • 性能决定了阶段 2 要建哪些索引、阶段 3 分页参数要不要夹取范围。
  • 安全决定了阶段 3 的密码哈希、JWT 鉴权、上传白名单——这三条写进文档,代码里就不会漏。
  • 兼容决定了阶段 4 用什么 CSS 写法、阶段 6 部署在哪个 Linux 发行版上。
避坑非功能需求必须可量化。「高性能」「要安全」「兼容主流浏览器」都不是需求,是口号——没人能验收。「列表接口 P95 < 500 ms」「密码用哈希存储、响应不含 password」「Chrome / Edge / Firefox 最新两个大版本」才是需求。写需求时记住一句:能被验证的,才叫需求。

原型草图:三个页面长什么样

先摆信息,再谈美观
先讲为什么线框图(wireframe)说人话就是「不要颜色的页面」:只有方框、线条和文字标签。它的价值在于——配色和动效可以最后调,信息层级必须最先定。
等你写前端时才发现「详情页原来还要放个收藏按钮」,那已经晚了:接口里没有 is_favorite 字段。
动手做用最简单的方框和线条画三个页面的草图:① 首页 ② 作品列表 ③ 作品详情。每个方框里标上「这块的数据从哪个接口来」。
古典文学网 首页 / 作品 搜索 登录 轮播图 Banner(3 张,自动切换) 数据:GET /api/banner/list 唐诗 宋词 元曲 散文 小说 文学推荐 GET /api/category/list · GET /api/article/recommend 念奴娇·赤壁怀古 苏轼 · 宋词 · ♡ 58 静夜思 李白 · 唐诗 · ♡ 42 春望 杜甫 · 唐诗 · ♡ 31
古典文学网 作品列表 筛选条件 分类:全部 / 唐诗 / 宋词… 朝代:唐 / 宋 / 元 排序:最新 / 最热 / 最多赞 查询 GET /api/category/list 搜索框:按标题 / 摘要模糊搜索(keyword) 静夜思 李白 · 唐诗 床前明月光,疑是地上霜… 浏览 328 · ♡ 42 念奴娇·赤壁怀古 苏轼 · 宋词 大江东去,浪淘尽… 浏览 412 · ♡ 58 声声慢 李清照 · 宋词 寻寻觅觅,冷冷清清… 浏览 298 · ♡ 47 天净沙·秋思 马致远 · 元曲 枯藤老树昏鸦… 浏览 187 · ♡ 22 分页:上一页 1 2 3 下一页 | 数据:GET /api/article/list?page=1&page_size=10
古典文学网 静夜思 李白 · 唐 · 唐诗 | 浏览 328 · ♡ 42 | 书评 2 条 ☆ 收藏 ♡ 点赞 is_favorite / is_liked 由后端回填 床前明月光,疑是地上霜。 举头望明月,低头思故乡。 正文 content(详情接口才返回,列表接口不返回) 书评(2) GET /api/comment/list 管理员:短短二十字,写尽思乡之情。 ♡ 5 回复 青莲居士:小时候背只觉得朗朗上口,长大后读才懂乡愁。 ♡ 8 发表书评框(需登录) POST /api/comment/add
讲透:三张图连起来是一条动线
  • 首页负责「拉人进来」:轮播图 + 分类入口 + 推荐位,对应 banner / category / recommend 三个接口。
  • 列表负责「筛出想要的」:分页、分类、朝代、搜索、排序——六个查询参数一个不多一个不少,正好是 /api/article/list 的参数表。
  • 详情负责「留住人」:正文、收藏、点赞、书评——所以详情接口的返回里必须有 content、is_favorite、is_liked、comment_count。
  • 这张草图就是接口字段的来源。页面要显示什么,接口就得返回什么——这就是「先画原型、再定接口」的原因。

数据库设计概览:7 张实体

表是后端的骨架
先讲为什么页面上的每一块内容,最终都要有地方存:轮播图存哪儿?书评的「回复」怎么表示?点赞怎么保证一个人只点一次?
数据库设计就是回答这些问题的过程。它做完之后,后端的模型、接口的字段、前端的列表——全都跟着它走。表设计错了,是全部推倒重来。
user username / password category name / sort banner title / image / link article title / dynasty / views favorite uk:user + article comment content / parent_id user_like target_type / target_id 1:N 1:N 1:N 1:N 写书评 1:N 收藏 自关联(楼中楼) banner 与 user_like 无外键:前者独立配置,后者靠 target_type + target_id 逻辑关联
讲透:这一节只定到「表 + 关系」,字段级细节留到阶段 2
  • 为什么分两步:概念设计(有几张表、谁指向谁)先评审一遍,通过了再落成 DDL。否则字段名改十遍,表名还在改。
  • 「回复」不用新表:comment.parent_id 指向同表的 id,一层自关联就搞定了楼中楼。
  • 「点赞」只用一个表:作品和书评的点赞结构完全一样,用 target_type 区分即可,不必建两张表。
  • 阶段 2 会把这张图逐张落成 CREATE TABLE、外键、索引,并补上视图 v_article_list。
避坑别在 article 里再存一个 category_name 字段——分类名是分类表的属性,冗余一份就意味着「改分类名时要遍历全表更新」,而且一旦漏更新就永久不一致。要拿分类名就 JOIN 或者查视图。
但 views / likes 是例外:它们是「计数器」,天生允许冗余,代价是每次点赞要同时改记录表和计数器——这一条在阶段 3 会重点处理。

接口设计先行:先把契约钉死

前后端分离的第一条纪律
先讲为什么前后端分离以后,前端和后端是两条线上的两个人。他们之间唯一的「合同」就是接口文档。
合同没签,前端就只能干等后端把接口写完——这是最典型的项目延期原因。把接口先定下来,两条线就能同时开工。
动手做 ①先定「所有接口长一个样」——统一响应结构。这一步做完,前端只需要写一套拦截逻辑,18 个接口通用。
接口契约 · 注册.jsonjson
// 请求:POST /api/user/register    Content-Type: application/json
{
  "username": "zhangsan",
  "password": "123456",
  "nickname": "张三",
  "email": "zhangsan@example.com"
}

// 响应:无论成功还是失败,永远是这三个字段
{
  "code": 200,
  "msg": "注册成功",
  "data": {
    "user_id": 3
  }
}
响应内容接口返回数据
运行结果: 失败时壳不变,只有 code 和 msg 变: { "code": 400, "msg": "用户名已存在", "data": null } HTTP 状态码统一返回 200,业务成败看 code 字段。
动手做 ②把 18 个接口列成一张表,写进设计文档。这张表要能一眼看出:哪些模块、怎么调、要不要登录。它必须和 API 接口文档 完全一致——不一致就是两份真相,等于没有真相。
接口设计.mdmarkdown
## 七、接口设计(BaseURL:http://127.0.0.1:5000/api)

| 模块 | 方法 | 路径 | 说明 | 需登录 |
|---|---|---|---|---|
| 用户 | POST | /api/user/register | 注册 | 否 |
| 用户 | POST | /api/user/login | 登录 | 否 |
| 用户 | GET | /api/user/profile | 获取个人信息 | 是 |
| 用户 | PUT | /api/user/profile | 修改个人信息 | 是 |
| 用户 | POST | /api/user/avatar | 上传头像 | 是 |
| 用户 | POST | /api/user/logout | 退出登录 | 是 |
| 首页 | GET | /api/banner/list | 轮播图列表 | 否 |
| 首页 | GET | /api/category/list | 分类列表 | 否 |
| 首页 | GET | /api/article/recommend | 文学推荐 | 否 |
| 作品 | GET | /api/article/list | 作品列表(分页 / 筛选 / 搜索) | 否 |
| 作品 | GET | /api/article/detail/<id> | 作品详情 | 否 |
| 作品 | POST | /api/article/like | 点赞作品 | 是 |
| 书评 | GET | /api/comment/list | 书评列表 | 否 |
| 书评 | POST | /api/comment/add | 发表书评 | 是 |
| 书评 | POST | /api/comment/like | 点赞书评 | 是 |
| 书评 | DELETE | /api/comment/<id> | 删除自己的书评 | 是 |
| 收藏 | POST | /api/favorite/toggle | 收藏 / 取消收藏 | 是 |
| 收藏 | GET | /api/favorite/list | 我的收藏 | 是 |
效果预览运行结果
运行结果: $ ls -1 docs/ 接口设计.md 需求与设计说明书.md
讲透:这张表后面会被三个阶段反复引用
  • 阶段 3照着它逐个实现——18 行就是 18 个视图函数,一行都不能少。
  • 阶段 4照着它写前端的请求封装——路径、方法、参数名全部对齐。
  • 阶段 5照着它设计测试用例——每行至少一条正向用例 + 一条异常用例。
  • 命名约定:路径用名词(/api/article/list),动作交给 HTTP 方法(GET 查、POST 增、PUT 改、DELETE 删);分页、排序、筛选一律走查询参数,不塞进路径。
避坑接口定稿后不要随手改字段名或路径。真要改,必须三处同步:接口文档、后端实现、前端请求——漏一处就是前端拿到 undefined,或者直接 404,而报错现场往往离真正的原因很远。
另外,鉴权信息放请求头(Authorization: Bearer <token>),不要放 URL——URL 会被浏览器历史、Nginx 日志、代理完整记录下来。

项目计划:7 个阶段拆什么

每阶段都要有能交出去的东西
先讲为什么项目里「做完了」这句话没有意义。得说清楚两件事:做完了什么(交付物)和怎么算做完(里程碑)。
所以计划表不是行程表,它是把「进度」变成可验证事实的工具。
阶段 主要工作任务 交付物 里程碑(怎么算做完)
阶段 1 · 需求与设计角色与权限、功能清单、非功能需求、原型草图、ER 图、接口设计、项目计划《需求与设计说明书》M1 设计评审通过,八个小节无「待补充」
阶段 2 · 数据库实施建库建表、外键与索引、视图、种子数据、常用查询可重复执行的 hiwen.sqlM2 SHOW TABLES 能看到 7 表 1 视图,种子数据齐全
阶段 3 · 后端开发应用工厂与蓝图、ORM 模型、统一响应、全局异常、JWT 鉴权、18 个接口可运行的后端服务M3 Postman 跑通「注册 → 登录 → 拿 token → 查个人信息」
阶段 4 · 前端开发页面与路由、状态管理、请求封装、与后端联调可点击的前端 SPAM4 三端联调通过,页面上数据都是真的
阶段 5 · 测试功能用例、异常用例、跨浏览器测试、缺陷修复测试用例与测试报告M5 P0 用例全部通过
阶段 6 · 部署上线服务器环境、MySQL 安装、Gunicorn 守护、Nginx 配置、HTTPS(可选)公网可访问地址M6 在别人电脑上能打开这个网址
阶段 7 · 答辩与复盘演示、讲设计取舍、答问、项目总结答辩 PPT + 项目总结M7 答辩通过,能讲清「为什么这么设计」
动手做先把目录建出来。后面的文档、脚本、Postman 集合都有固定位置放——位置固定了,别人接手时不用问「东西在哪」。
建目录.shbash可运行
# 在 pyweb/ 目录下执行
mkdir -p docs assets/postman
tree -L 1
终端输出命令行执行结果
运行结果: . ├── assets ├── code ├── docs ├── flask ├── mysql ├── sql └── vue 7 directories
讲透:里程碑是「卡点」,不是「日期」
  • M1 过不了就不许建表——设计没评审完就动手,改表会连带改接口和页面。
  • M3 过不了就不许写前端——后端接口没跑通,前端联调时你分不清是前端错还是后端错。
  • 每个里程碑都用「一条命令 / 一个网址 / 一个文件」来证明:M2 是一条 SHOW TABLES,M3 是 Postman 里一串绿勾,M6 是一个能打开的网址。

本阶段交付物清单

《需求与设计说明书》该有什么
先讲为什么交作业时最尴尬的一幕:老师说「设计文档呢」,你说「在我脑子里」。
文档不是走形式——它是你后面六个阶段的入口。阶段 2 照着第六节建表,阶段 3 照着第七节写接口,阶段 4 照着第五节写页面。
动手做按下面的骨架建文档,八个小节一个都不能少。骨架先立起来,再往里填内容——比想到哪儿写到哪儿快得多。
需求与设计说明书.mdmarkdown
# 古典文学网 · 需求与设计说明书

## 一、项目背景与目标      —— 一句话说清「做什么、给谁用」
## 二、用户角色与权限      —— 游客 / 注册用户 / 管理员,各自能做什么、不能做什么
## 三、功能清单            —— 模块 | 功能点 | 优先级(P0 / P1 / P2),共 18 条
## 四、非功能需求          —— 性能 / 安全 / 兼容性,每条都要可量化、可验证
## 五、原型草图            —— 首页 / 作品列表 / 作品详情三张线框图
## 六、数据库设计          —— 7 张表与关系(ER 图),字段级 DDL 留到阶段 2
## 七、接口设计            —— 统一响应结构 + 18 个接口总览表
## 八、项目计划            —— 7 个阶段的任务、交付物与里程碑

附:术语表(作品 / 书评 / 收藏 / 点赞 / 轮播图 / 楼中楼)
效果预览运行结果
运行结果: $ tree docs/ docs/ ├── 需求与设计说明书.md ├── 原型草图 │ ├── 1-首页.svg │ ├── 2-作品列表.svg │ └── 3-作品详情.svg ├── 数据库设计.md └── 接口设计.md 2 directories, 5 files
讲透:评审时老师会问四个问题,你对照清单自测一遍
  • 「谁在用」——角色与权限写清了吗?游客能不能发表书评?
  • 「做什么」——18 条功能点能不能和 18 个接口一一对上?
  • 「数据存哪儿」——7 张表够不够?书评的回复怎么表示?点赞怎么去重?
  • 「怎么算做完」——每个阶段的交付物和里程碑有没有写死?M3 的验收动作是什么?
这四个问题答得上来,M1 就过了。
避坑文档里不要出现「待补充」「后续再定」「大概类似」这类词。每留下一个模糊点,最后都会在代码里变成一处硬编码,或者在联调时变成一场争论。定不下来就先写一版最简的,标注「V1 暂定」并且给出暂定的具体方案——有方案就能开工,没方案就只能等。
动手练习
1写出「古典文学网」的三个用户角色,并给每个角色各写一条「不能做什么」。写完后自问:这条限制落到后端,应该由哪个接口来拦?(提示:想想 401 和 403 分别在什么情况下出现。)
2把下面的需求翻译成可验收的非功能需求:① 「网站要快」② 「密码不能泄露」③ 「手机上也要能用」。每一条都要给出「目标」和「怎么验证」两列。
3迁移练习:「红影赏析」是一个电影主题的网站。请为它写一份精简版需求与设计:① 功能清单 3 条(都标 P0);② 数据库设计 2 张表(movie / review);③ 接口设计 4 个(路径 + 方法 + 是否需登录)。想一想:它和古典文学网有多少是可以直接搬过来的?
参考答案
红影赏析 · 需求与设计.mdmarkdown
## 三、功能清单

| 模块 | 功能点 | 优先级 | 对应接口 |
|---|---|---|---|
| 电影 | 电影列表(分页 + 类型筛选 + 评分排序) | P0 | GET /api/movie/list |
| 电影 | 电影详情(含影评数) | P0 | GET /api/movie/detail/<id> |
| 影评 | 发表影评(需登录) | P0 | POST /api/review/add |

## 六、数据库设计

movie   :id / title / director / genre / rating / created_at
review  :id / movie_id(外键 → movie.id)/ user_id / content / created_at

关系:一部电影对多条影评(1:N),删电影要不要删影评 → ON DELETE CASCADE

## 七、接口设计

| 模块 | 方法 | 路径 | 说明 | 需登录 |
|---|---|---|---|---|
| 电影 | GET  | /api/movie/list       | 电影列表(page / genre / order_by) | 否 |
| 电影 | GET  | /api/movie/detail/<id> | 电影详情 | 否 |
| 影评 | GET  | /api/review/list      | 影评列表(movie_id 必填) | 否 |
| 影评 | POST | /api/review/add       | 发表影评 | 是 |
效果预览运行结果
运行结果: $ ls -1 docs/ 红影赏析-需求与设计.md 古典文学网-需求与设计.md

第 3 题的关键:换的是「实体名」(作品 → 电影、书评 → 影评、分类 → 类型),不变的是整套骨架——角色权限、功能清单、ER 图、接口表、P0 优先。这份骨架你能迁移过去,说明你掌握的是方法,不是某个网站的细节。

本阶段小结