参与编写两本教材:网络服务篇的六个项目、全书排版与综合实验
项目速览
《openEuler 操作系统》 《物联网数据库技术》 出版信息 机械工业出版社,ISBN 978-7-111-80354-6,2026.03 东南大学出版社,ISBN 978-7-5766-2638-4,2026.04 规模 16 开、257 页、40.7 万字 16 开、346 页、57.6 万字 全书结构 系统认知篇、系统管理篇、网络管理篇,共 12 个项目 基础篇、数据管理篇、物联网数据库应用篇 我承担的部分 网络管理篇(项目 7–12,共 6 个项目)的编写与实验验证 参编:第 12 章综合实验、全书排版统稿、课后习题答案整理 涉及技术 Linux 网络配置、firewalld、SELinux、DHCP、BIND、MySQL 主从、NFS/FTP/Samba、Apache MySQL、SQL、JDBC/JSP、时序数据、智能农业监控系统
这两年我先后参与了两本教材的编写。一本是《openEuler 操作系统》,我负责网络部分,也就是网络管理篇的六个项目;另一本是《物联网数据库技术》,我作为参编,负责第 12 章的综合实验、全书的排版统稿,以及课后习题答案的整理。
这两本书现在都已经出版。写这篇文章,是想把过程中的一些做法和遇到的问题记录下来。这些内容对写技术教程的人可能有些参考价值,对我自己也是一次复盘。
一、教材和技术文章的区别
平时写技术文章,我习惯写"我是怎么解决的"。环境可以假设,步骤可以省略,因为读者大多有基础,看得懂。
教材不能这样写。教材的读者是几百个第一次接触 Linux 的学生,他们坐在机房里,遇到报错的第一反应是怀疑书上写错了。书里一个写错的路径、一句漏掉的 systemctl restart,影响不是一个班级的一节课,而是这本书整个生命周期里的每一届学生。
所以在动笔之前,我先给自己定了几条规矩。
每个实验都在干净快照上从零跑一遍。 不是"在我这个环境里是好的",而是把虚拟机恢复到刚装完系统的状态,从第一条命令开始敲,中间不做任何书上没写的操作。这一条筛出了不少问题:有些软件包是之前做实验时顺手装的,书里没写安装步骤,学生照着做就会遇到 command not found;还有些正文里贴的配置文件是我早期版本的内容,和自己后面改过的实际配置对不上。
版本要写死,差异要写出来。 以 openEuler 为实践平台有一个现实问题:yum 和 dnf 并存,BaseOS 和 AppStream 两个仓库的分工,不同 LTS 版本里同一个服务的默认配置也有差别。教材里不能写"视版本而定",那样等于把问题留给学生。我的做法是明确写清以哪个版本为准,在容易出岔子的地方补一句说明,比如"若所用版本默认使用 dnf,将下文的 yum 替换为 dnf 即可,参数一致"。
关键步骤要给出预期输出。 学生往往分不清一条命令是执行成功了,还是执行了但没生效。所以凡是有验证作用的步骤,正文里都附上应该看到的结果,比如 systemctl status 里的 active (running)、named-checkzone 输出的 OK、SHOW SLAVE STATUS 里的两个 Yes。学生把自己屏幕上的输出和书对一下,就知道该继续还是该回头检查。
另外还有体例的约束。这套书是项目化教材,每个项目固定八个环节:项目导入、学习导览、学习目标、项目准备、项目实施、知识拓展、项目小结、练习题。这个结构不是排版上的要求,而是教学上的安排——先给场景,再给目标,然后动手,最后才是拓展和检验。写的时候必须严格照着来,自己发明结构,整本书就会显得散。
二、网络管理篇的六个项目
网络管理篇覆盖项目 7 到项目 12,内容是"一台服务器从能联网,到能对外提供几种常见服务"。
| 项目 | 内容 | 核心配置对象 |
|---|---|---|
| 项目 7 网络基本操作 | 主机名、NetworkManager、firewalld、SELinux | nmcli、firewall-cmd、getenforce |
| 项目 8 搭建 DHCP 服务器 | 地址自动分配、保留地址 | dhcpd.conf 的 subnet / host 声明 |
| 项目 9 搭建 DNS 服务器 | 正反向解析、主从 | BIND 的主配置、区域文件、数据文件 |
| 项目 10 搭建数据库服务器 | MySQL / MariaDB、主从复制 | my.cnf、server-id、binlog |
| 项目 11 搭建文件共享服务器 | NFS、FTP、Samba | exports、vsftpd.conf、smb.conf |
| 项目 12 搭建 WWW 服务器 | Apache、虚拟主机 | httpd.conf、vhost 配置段 |
把网络服务拆成六个项目,而不是合成一个大项目,是有考虑的。如果合成一个大项目,学生做完之后发现服务不通,面对的是十几个配置文件和好几个服务,不知道从哪里查起。拆成六个之后,每个项目只引入一个新内容,前一个项目的成果是后一个项目的前提,出问题时范围就限定在这一次改动的部分。这也是这套书反复强调的"在学习中实践,在实践中学习"。
写这六个项目,真正花时间的不是把命令敲对,而是把"配完了但不通"的各种情况提前想到,写进书里。下面是几个印象比较深的点。
2.1 防火墙和 SELinux
openEuler 这一系的系统,服务起不来或者起来了访问不到,多数时候不是配置文件写错,而是被防火墙和 SELinux 拦住了。教材里如果图省事写一句"关闭防火墙和 SELinux",学生学到的是回避问题的做法,以后到了真实环境里会吃亏。
所以我的写法是让学生把这两层都开着,把服务配通,再回过头解释每一层为什么拦截、怎么精确放行。
# 防火墙:按服务放行,而不是关闭整个防火墙
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=dns
firewall-cmd --reload
firewall-cmd --list-services # 预期输出中应包含 http 和 dns
# SELinux:先看状态,再决定改什么
getenforce # Enforcing 表示正在拦截
getsebool -a | grep httpd # 查看与 httpd 相关的布尔值
SELinux 这部分最容易写得含糊。一个典型的现象是:把网站文件放在 /web 而不是默认的 /var/www/html,权限是 777,防火墙也放行了 80 端口,但浏览器访问仍然是 403。原因不在权限,而在文件的安全上下文。这个现象如果不写清楚,学生只会得出"Linux 有问题"的结论。
semanage fcontext -a -t httpd_sys_content_t "/web(/.*)?"
restorecon -Rv /web
ls -Z /web # 确认上下文已变为 httpd_sys_content_t
Samba 一节是同样的情况。共享目录建在 /srv/share 下,smb.conf 写得再对,smbclient 也连不上,因为 SELinux 不认为这个目录是共享目录:
semanage fcontext -a -t samba_share_t "/srv/share(/.*)?"
restorecon -Rv /srv/share
setsebool -P samba_export_all_rw on
这里我专门写了一段 chcon 和 semanage fcontext 的区别:前者是临时修改,restorecon 一执行就还原;后者写进策略库,是永久生效的。这一点在不少书上被跳过了,结果是学生过几天发现"昨天还好好的,今天又不行了"。
2.2 DNS 的两个坑
DNS 是六个项目里最难写的一个。它不是配一个文件,而是配几个互相关联的文件,任何一处不一致,表现都是同一个症状:dig 查不到记录。
第一个坑是语法。BIND 的区域文件用分号作注释,用括号做跨行续写,域名末尾的点表示绝对域名。少写一个分号,named 服务起不来,而 systemctl status named 给出的报错只指向行号,不解释原因。我把这个报错的原文贴在正文里,然后逐行解释,比只写一句"注意语法"要有用。
第二个坑是 SOA 记录里的序列号。修改了区域文件之后,主服务器立刻生效,从服务器却不更新——因为序列号没有递增。这不是能靠重新加载绕过去的问题,是 DNS 增量同步机制的设计。所以正文里必须明确写:每次修改区域文件,序列号都要加一。
$TTL 1D
@ IN SOA ns.lab.local. root.lab.local. (
2025030101 ; serial —— 修改区域文件后必须递增
1H ; refresh
15M ; retry
1W ; expire
1D ) ; minimum
IN NS ns.lab.local.
ns IN A 192.168.10.10
www IN A 192.168.10.20
写完配置,还要跑校验,不能靠"服务没报错"来判断:
named-checkconf /etc/named.conf
named-checkzone lab.local /var/named/lab.local.zone
2.3 文件共享和主从数据库
项目 11 讲 NFS、FTP 和 Samba 三种文件共享。写这一节时我用了一个办法:同一个目标——"把一个目录共享给同网段的其他机器"——分别用三种协议实现一遍。NFS 是三行 exports 加一条 exportfs -arv;FTP 是匿名目录加权限设置;Samba 是 smb.conf 里一个共享段加用户认证。三种做法放在一起,学生能比较自然地理解协议和场景的关系:Linux 之间用 NFS 简单,要照顾 Windows 客户端就用 Samba,跨公网传文件才考虑 FTP。
项目 10 的主从复制,关键配置只有三处,但每一处漏了都表现为"连接正常但数据不同步":
# 主库 my.cnf
server-id = 10
log-bin = mysql-bin
binlog_format = ROW # 行格式,避免不确定函数导致主从不一致
# 从库 my.cnf
server-id = 11 # 必须与主库不同,重复会导致复制中断
验证不写"看日志有没有报错",而写"看两个 Yes":
SHOW SLAVE STATUS\G
# 预期:Slave_IO_Running 与 Slave_SQL_Running 均为 Yes
三、排版和统稿
第二本书我负责全书排版。这件事在外人看来是"调格式",实际做起来是一份不小的工程:346 页的书,几十张图、上百个表格和代码块,十几位作者的稿子要合成一份能交付印刷的完整书稿。
做下来的体会,核心就一条:用样式,不用手工格式。
3.1 标题和编号
手动加粗标题、手动敲"7.1.1"这种编号,在几十页的文档里勉强能用,在三百多页的书里不行。中间插入一个小节,后面几十处编号、目录页码、正文里所有"如图 7-3 所示"都要手工改,改漏一处就是印刷错误。
我的做法是把多级列表和标题样式绑定:一级标题对应"第 X 章",二级对应"X.X",三级对应"X.X.X",编号由 Word 自动生成。之后插入、删除、调整顺序,编号会自己跟着变。
图表编号同理。图 7-1、表 7-1 都通过"插入题注"生成,编号包含章节号;正文里引用图表用"交叉引用",不手打。改稿期间有几章的图调整过顺序,如果编号是手打的,需要人工核对两百多处;用交叉引用,全选按 F9 更新一次就完成。目录也是 TOC 域而不是手抄的页码,交付前更新全部域。
3.2 分节和版面
每章另起一页,用"分节符(下一页)",不用回车凑。页眉、页脚、页码都是按节生效的:正文用阿拉伯数字页码,前言和目录用罗马数字,奇数页页眉放书名、偶数页放章名,这些都靠分节符实现。用回车凑出来的分页,内容一增删,页码就乱了。
图片和代码有一些针对印刷的要求:截图用同一台虚拟机、同一套终端配色和字号,保证全书风格一致;导出 300 dpi,按版心宽度等比缩放;代码块用等宽字体,中文注释单独指定中文字体,否则会显示成方框;表格统一用三线表,不跨页断行;正文宋体小四、1.5 倍行距,标题字号按出版社的模板固定;打开"孤行控制",避免标题落在页面最后一行。
3.3 多人协作时的版本问题
十几位作者一起写一本书,交付前最大的风险不是内容有错,而是版本混乱——没人说得清哪个文件是最新的。
我的做法是:一章一个文件,统一命名规则(第07章_网络基本操作_v3_日期.docx);统稿时用主控文档把各章合并成一份整稿,合并前先确认各章的版本号,合并后打上整稿版本号并冻结;改稿意见通过批注和修订模式回传,不接受"改好的最终版2.docx"这种命名互相覆盖。
交付前还有一张检查清单,逐项核对:
| 检查项 | 具体内容 |
|---|---|
| 编号连续性 | 章、节、图、表、公式编号无跳号、无重复 |
| 交叉引用 | 全选 F9 更新后,无"错误!未找到引用源" |
| 目录 | 与正文标题一致,页码对齐 |
| 术语统一 | 同一概念全书用同一个叫法,不混用 |
| 图表清晰度 | 无缩放模糊,无截图带多余的窗口边框 |
| 代码可复制 | 代码块无自动断字、无全角空格 |
| 参考文献 | 格式统一,正文引用与文末条目一一对应 |
四、第 12 章的综合实验
第二本书的第 12 章是全书唯一的综合实验章,由我负责编写。它的定位是:把前面十一章讲的内容——E-R 设计、建表、增删改查、多表连接、视图索引、存储过程、JDBC——合成一个能运行的完整系统。
这类章节很容易写坏,写坏的方式就是变成知识点的第二次罗列。我采取的办法是只保留一条主线:贯穿全书的智能农业(温室大棚)监控系统,从需求分析开始,到数据库设计、建表、造数据、查询,最后到页面展示,一路走完。
实验和演示的另一个区别是,实验要能被教师判分。所以每一步都规定了明确的、可观察的交付物:
| 检查点 | 学生交付物 | 判分依据 |
|---|---|---|
| 需求与 E-R | 实体、属性、联系图 | 是否覆盖设备、传感器、采集记录、告警四类实体及其联系 |
| 建表 | DDL 脚本 | 主外键正确,字段类型合理,字符集统一为 utf8mb4 |
| 造数据 | 数据脚本执行结果 | 各表记录数与预期一致,外键无悬挂 |
| 查询 | 指定场景的 SQL | 结果集的列名、行数、排序与预期一致 |
| 程序访问 | JDBC 连接与查询输出 | 能读出实时状态与历史数据 |
| 页面展示 | 带分页和条件筛选的列表页 | 分页边界正确,筛选条件组合正确 |
数据准备部分,我配套提供了初始化脚本,一条命令建库、建表、灌数据:
SOURCE init_schema.sql;
SOURCE init_data.sql;
SELECT COUNT(*) FROM t_collect; -- 预期:与正文标注的行数一致
原因很实际:一个监控系统要有说服力,采集记录表得有几万行数据,让学生手敲 INSERT 不现实,复制粘贴几百行也必然出错。脚本里还故意留了一两条边界数据,比如某条采集记录的时间戳早于设备上线时间,留给数据清洗那一步做练习。
机房环境是不能指望的。学生机器上装不上 MySQL、JDK 版本不对、连不上云端数据库,这些是常态。所以这一章除了主路径(本机安装 MySQL,用 JDBC 连接),还写了一条备用路径(连接教师提供的云端实例),并整理了一张常见故障对照表:
| 现象 | 原因 | 处理 |
|---|---|---|
Public Key Retrieval is not allowed |
MySQL 8 默认认证插件与旧驱动不匹配 | 连接串加 allowPublicKeyRetrieval=true&useSSL=false |
中文显示为 ??? |
连接串未指定字符集,或库表不是 utf8mb4 | 连接串加 characterEncoding=utf8,核对库表字符集 |
Communications link failure |
服务未启动、端口未放行、绑定地址为 127.0.0.1 | 检查服务状态、防火墙、bind-address |
| 时间差 8 小时 | 服务端时区与 JVM 时区不一致 | 连接串指定 serverTimezone=Asia/Shanghai |
这张表的做法很朴素:把我在跑通实验时真实遇到的报错一条条记下来,再逐条查原因。写完之后回头看,这张表可能是整章里被学生翻得最多的部分。
五、答案整理
第二本书全部课后习题的答案也由我整理。这件事看起来机械,实际上是全书质量的最后一道关卡。
操作题的答案不能只给结论。一道"配置 DHCP 并为某台主机保留固定地址"的题,只写"在 dhcpd.conf 中添加 host 声明"是没有用的,学生照着写还是会错。答案要给到可以直接执行的配置片段,再附上验证方法——客户端 ip addr 看到的地址、服务端租约文件里的记录。
答案要分层。基础题给标准答案;开放题(比如"为某个场景设计数据库表结构")给评分要点:必须有哪些表、哪些字段、哪些约束,做到什么程度算完整。这样教师判分有依据,学生也不会因为自己的方案和参考答案不一样就以为错了。
最容易被忽略的一步,是正文改了之后答案要跟着改。统稿阶段我调整过几章的小节顺序和例题编号,答案里"见第 8 章 8.2 节"这类引用随之全部失效。解决办法是用编号做锚点而不是页码,交付前拿一份"题目编号—答案编号"的对照表逐条核对一遍。这一步没有技巧,只能老老实实做,漏掉的后果也很直接:印出来的书上,答案是错的。
六、几点感受
写完这两本书,有几件事想明白了一些。
一是可复现性是技术写作最重要的指标。判断一段教程好不好,不看文字流畅不流畅,看一个新手照着做能不能得到同样的结果。这个标准也反过来影响了我的代码习惯:现在写脚本会主动写清前置条件、版本和预期输出,因为我知道下一个看它的人,很可能是三个月后的我,而他没有我现在的上下文。
二是排版不是美化,是结构。样式、题注、交叉引用、分节符,看起来是格式问题,本质上是把文档的结构变成可维护的数据。用样式写出来的书,调整结构是改一处的事;用手工格式堆出来的书,调整结构是改两百处的事。
三是写教材逼着我把"我知道"变成"我能讲清楚"。会配一个服务,和能把一个服务讲明白,是两种能力,后者要求你知道每一步为什么这么做、不这么做会怎样、做错了是什么现象。这个过程中积累下来的排错路径,比配置本身有用——配置随时可以查文档,排错路径只能靠踩。
最后说明一点:两本书的封面署名都是主编,我承担的是具体的章节编写和统稿工作。一个 257 页、一个 346 页,两本书都从初稿走到了印刷。回头看,那些"我以为写清楚了、其实没写清楚"的地方,都是靠一遍遍重跑实验和一遍遍统稿发现出来的。

浙公网安备 33010602011771号