难度:★☆☆(先具备基础语法与运行能力,可先学《编程起步》)
教材:查资料/(1456 行的 server.py + 一个前端文件夹)
预计时间:第一遍 40 分钟,练习 20 分钟
学完你能:围绕一次具体操作,画出入口、规则、数据与显示的调用链,并用运行结果核对
本课只要求交付一条有证据的调用链:输入标题 → 接口 → 数据库 → 列表显示。先能运行 Python 文件、理解函数和字典;不会时先完成 入门课程。
下载课程 ZIP,解压后在 assets/主线实验/ 打开终端,运行 py -3 server.py,再打开 http://127.0.0.1:8030。macOS/Linux 把解释器命令换成 python3。它只用标准库,不依赖 AI Key 或历史业务项目的完整环境。
输入“读懂 HTTP”并保存,刷新页面应仍有这张卡片。第一次运行空列表也是正常结果,不要求有预置数据。终端此时一直等待请求,Ctrl+C 才结束。
| 位置 | 要找的证据 | 读完能回答 |
|---|---|---|
| index.html 的 submit 监听 | JSON.stringify 中的 title | 输入框的文本怎样进入请求体 |
| server.py 的 do_POST | 路径检查与 json.loads | 哪个函数接到了请求,如何读正文 |
| card_store.py 的 create_card | validate_title、INSERT、with conn | 规则在哪里,哪一步提交数据库 |
| index.html 的 refresh | GET、创建 li、textContent | 为什么服务端成功后页面能显示新数据 |
先不要逐行读 CSS、请求头、数据库连接细节。每次只回答“这份 title 现在是什么类型,下一步交给谁”。例如请求体是 UTF-8 字节,解析后是字典,body.get("title") 才取得字符串。
入口是 server.py 末尾创建服务的代码;网络层是 Handler;存储层是 card_store;展示层是 index.html。入口在启动时运行,do_POST 在收到请求时由服务器调用。函数定义的位置先后,不代表每次点击都会从文件第一行重新执行。
回到下面的「查资料」地图时,按相同职责寻找对应函数。先按名字搜索,再核对历史教材版本;不要把固定行号当作跨版本保证。
把保存标题换成“阅读计划”,只观察 Network 和列表,不改后端。预期 POST 请求体 title 和响应 title 一致;刷新 GET 后仍能看见它。将四个位置连成箭头,并为每条箭头写上数据形状。
服务起不来先看终端错误:端口占用可加 --port 8032;打不开网页先确认地址和端口;列表为空先查 POST 是否成功;POST 成功却刷新丢失,检查是否换了数据库路径或工作目录。
验收:能指出实际请求、对应函数、参数化 INSERT、提交点、渲染点,不只交一张目录树。这个证据链比“十分钟读完任何项目”更可靠。
下面进入历史业务项目对照;其分发清单决定哪些文件是阅读摘录,完整运行环境以对应 README 为准。
很多初学者的顺序是错的:拼命写代码,却从不读代码。
但现实是:
读代码不是天赋,是有步骤的手艺。 这一课就把步骤教给你。
打开 查资料/,双击 启动服务.bat(或者命令行 py server.py),
然后浏览器打开 http://127.0.0.1:8000/。
先别管代码,点一点、用一用:
http://你电脑IP:8000/mobile 还能用(需要 --host 0.0.0.0 启动)为什么第一步是跑? 因为你要先知道"这段代码活着的时候是什么样"。
读代码时,每一行都可以对应到"哦,这就是刚才那个按钮干的事"。
程序总有一个"从哪开始执行"的地方。常见入口:
| 项目类型 | 入口怎么找 |
|---|---|
| Python 脚本/服务 | 找 if __name__ == "__main__": |
| Node/前端 | package.json 里的 scripts |
| 纯网页 | index.html |
「查资料」的入口是 server.py 最后两行(第 1455-1456 行):
if __name__ == "__main__":
main()
意思是:直接运行这个文件时,执行 main() 函数。
顺着 main()(第 1416 行)往下看,你会发现它做了四件事:
--host、--port)init_db() —— 建数据库表seed_demo_data() —— 没有数据时塞几条演示数据ThreadingHTTPServer(...).serve_forever() —— 启动服务器,一直等着别人访问到这里你就明白:这是一个"先准备数据,然后一直对外提供服务"的程序。
别从头读到尾。 1456 行的文件,人眼从头读到尾只会睡着,还会迷路。
正确姿势:先列出所有的函数和类,然后按"职责"分组。方法:
在 VS Code / 记事本里搜索 def 和 class (Python),或者看编辑器的"大纲"面板。
这是我替你整理好的「查资料」真实地图(行号为起始处):
server.py
│
├── ① 配置区(第 1-60 行)
│ 作用:读 config.json / 环境变量,决定用真实 AI 还是离线演示
│ 关键:_load_config_file()、AUTHORITY_SOURCES(权威信源白名单)
│
├── ② 数据层(第 475-610 行)
│ 作用:只管数据库,不管网页
│ 关键:db() 连接、init_db() 建表、card_by_id() 查、validate_sources() 校验
│
├── ③ 智能与搜索层(第 611-760 行)
│ 作用:AI 回答、全文搜索、语义搜索
│ 关键:_ai_stream_real()、fulltext_search()、_tfidf_vectors()、_cosine()
│
├── ④ 导出与渲染层(第 755-905 行)
│ 作用:把卡片导出成 Markdown/HTML、把 Markdown 渲染成网页
│ 关键:export_md()、export_html()、md_to_html()
│
├── ⑤ 网络层(第 908-1350 行)
│ 作用:接收浏览器请求,分发到上面各层,把结果发回去
│ 关键:Handler 类(do_GET / do_POST / do_DELETE 是路由入口)
│
└── ⑥ 启动层(第 1354-1456 行)
作用:初始化 + 启动服务
关键:seed_demo_data()、lan_ips()、main()
看到没有?一个 1456 行的文件,其实是 6 个模块拼起来的。
每个模块只干一类事情,这就是"模块化"最朴素的样子。
validate_sources(校验来源)、export_md(导出 md)→ 干活的Handler(处理器)→ 负责一类角色的_ 开头的(如 _json、_load_config_file):作者的"内部小工具",不打算给外面用(Python 的约定,不是强制)
do_GET / do_POST:固定名字!这是 Python 标准库规定的,只有叫这个名字,服务器收到请求才会调用它(第 02 课细讲)
光有地图还不够,要会"顺藤摸瓜"。选一个功能,从用户点击一路追到数据落地。
案例:在网页上新建一张卡片时,代码里发生了什么?
用户点击"保存"
↓
前端 js 发请求:POST /api/cards(web/js/ 里)
↓
server.py 的 do_POST() 第 1023 行:path == "/api/cards"
↓
调用 _card_create() 第 1092 行
↓
├─ 校验:标题、来源合法性(validate_sources)
├─ 组装数据:生成 id(uuid)、时间(now_str)
└─ 写数据库:INSERT INTO cards ...
↓
_json() 把结果打包成 JSON 发回浏览器
↓
前端收到后刷新列表
追完这一条线,你就同时看懂了:**请求怎么进来(网络层)、数据怎么存(数据层)、
结果怎么回去(网络层)、界面怎么更新(前端)**。
技巧:追数据流优先追"写入"和"查询",因为它们最能体现数据结构。
do_GET / do_POST 和相邻的一个业务函数init_db() + row_to_card()web/ 文件夹,别碰 server.py不要为了"读完"而读。 项目是工具书,不是小说。
你去 GitHub 上随便打开一个认真维护的 Python 项目,会发现也是这个套路:
| 教材项目 | 常见开源项目 | 作用 |
|---|---|---|
server.py 顶部注释 + 说明 | README.md | 告诉别人这是什么、怎么跑 |
web/ | static/ 或 public/ | 前端资源 |
config.example.json | .env.example | 配置模板(不含密钥) |
knowledge.db | 数据库文件或迁移脚本 | 数据 |
if __name__ == "__main__" | 同左(或 entry_points) | 统一入口 |
你会发现「查资料」已经是一个"小型开源项目"的样子了。
差别在于:大项目会把 server.py 按上面的 6 个模块拆成 6 个文件(甚至 6 个文件夹)。
拆不拆只是规模问题,模块思想是一样的——这正好是第 06 课的内容。
亲手确认一遍(5 分钟)
worry_debate_game/run.py,找出它的入口和「查资料」有什么不同一个用 uvicorn.run,一个用 ThreadingHTTPServer
do_GET 开始,追出"获取单张卡片"的完整链路(提示:/api/cards/ 开头 → _card_get → card_full),把链路写在纸上
练习做没做只有你自己知道 —— 但做与不做的差别,一动手就现形。
| 术语 | 人话解释 |
|---|---|
| 入口(entry point) | 程序开始执行的地方 |
| 模块 | 一块职责单一的功能代码,可以是一个文件、一个文件夹或一段区域 |
| 数据流 | 一次操作中数据经过的路径:从哪来 → 怎么变 → 到哪去 |
| 标准库 | Python 自带的工具箱(如 sqlite3、http.server),不用安装就能用 |
| 第三方库 | 需要 pip install 才能用的库(如 FastAPI、PySide6) |
第 02 课:手写 HTTP 服务器。「查资料」没用 Flask/FastAPI,
而是用 Python 标准库自己撸了一个服务器——我们把 Handler 类拆开看,
并对照 Flask 和 FastAPI 的写法,看看"框架到底帮你干了什么"。