从零搭建一个英语绘本在线阅读系统:我的实践与思考
缘起
让孩子在家有一个方便、好用的英语绘本阅读环境,是我做这个系统的初衷。市面上的英语绘本 App 不少,但要么内容封闭、要么功能零散,很难把"阅读—跟读—查词—统计"串成一条完整的链路。于是我决定自己动手,用 PHP + MySQL + 原生前端写一套轻量的英语绘本在线阅读系统。
这篇文章介绍这套系统的功能设计与技术实现,既是对已完成工作的梳理,也希望给有类似需求的朋友一些参考。
系统概览
整套系统分为四个角色页面:登录页、用户仪表盘(Dashboard)、阅读器(Reader)、管理后台(Admin)。用户登录后进入 Dashboard 选书、查看统计;点击书籍进入 Reader 阅读;管理员通过 Admin 后台上传绘本、管理用户和发布公告。
技术栈很朴素:前端是纯 HTML/CSS/JavaScript,没有使用任何框架;后端是一个 api.php 文件统一处理所有请求,基于 action 参数做路由分发;数据库用 MySQL,图片处理依赖 ImageMagick。部署在 nginx + PHP-FPM 上。
选择这种"零框架"的方式并非偷懒,而是考虑到系统的目标用户规模不大(家庭或小班级),维护者可能也只有自己一人,减少依赖意味着减少长期维护成本。
绘本管理:从 PDF 到可阅读
批量上传与自动转换
管理员在后台选择一个分级(如 RAZ 的 A、B、C 等),拖拽上传多个 PDF 文件。系统对每个 PDF 依次执行三步操作:保存原始文件、通过 ImageMagick 生成封面缩略图(300px 宽、JPEG 格式)、将每一页转换为 JPEG 图片(150dpi、最大宽度 1600px、质量 75%)。
系统最初使用的是 PDF.js 在浏览器端直接渲染 PDF,但实际运行中受限于服务器带宽,大文件的传输不够流畅,翻页体验打折扣。后期改为服务端预转换方案:上传时通过 ImageMagick 将每一页转为压缩后的 JPEG 图片,阅读时按需加载单张图片,单页体积大幅缩小,翻页变得丝滑。转换过程逐页进行以控制内存占用,ImageMagick 的资源限制设为 512MB 内存、1GB 内存映射、4GB 磁盘。
分级体系
系统支持多套分级体系(Reading System),目前内置了 RAZ(Reading A-Z)的 27 个级别,从 aa 到 Z。每个级别下可以挂载若干绘本,Dashboard 左侧按级别导航,并显示每个级别的完成进度(已读/总数)。
音频匹配
绘本的配套音频是教学中的刚需。我写了一个 CLI 脚本 match_audio.php,扫描 audio/ 目录下的音频文件,通过标题归一化、模糊匹配(similar_text 函数,60% 以上阈值)、子串包含三种策略,自动将音频文件与绘本关联。匹配结果导出为 CSV,管理员人工审核后,另一个脚本 apply_audio_match.php 将高置信度和中置信度的匹配写入数据库。这比逐本手动关联高效得多。
阅读器:核心体验
阅读器是整套系统最花心思的部分,所有交互逻辑集中在 reader.html 一个文件里。
翻页模式
支持三种阅读模式:单页、双页(模拟翻开书的左右两页)、以及"分裂模式"——将一页上下分割,下半部分旋转 180 度,模拟把书摊开在桌面上的效果。用户可以通过点击屏幕左右 30% 区域、键盘方向键、触摸滑动、底部进度条拖拽、以及直接输入页码来翻页。相邻页面会预加载到缓存中,保证翻页流畅。
缩放与平移
鼠标滚轮可以在 1x 到 5x 之间缩放,缩放以鼠标位置为中心点。放大后可以拖拽平移,带边界约束。缩放时会在画面中央短暂显示当前缩放百分比。
三级音频逻辑
音频播放遵循一个清晰的优先级:如果绘本有配套音频(管理员上传),播放配套音频;如果没有配套音频,但用户自己录过音,则播放用户的录音,并标注"我的录音";如果两者都没有,显示一个静音图标。这样用户在任何情况下都能得到明确的音频状态反馈。
浏览器内录音
点击麦克风按钮,系统通过 MediaRecorder API 开始录音(webm 格式),界面上出现脉动的红点和计时器。录音结束后通过 FormData 上传到服务器,保存在 uploads/recordings/{user_id}_{book_id}.webm。录音功能不仅可以在阅读器中使用,也可以在 Dashboard 的书籍详情中直接录制,方便用户在不打开书的情况下快速跟读。
查词词典
顶部搜索栏支持输入英文单词,300ms 防抖后调用后端查询。后端连接一个独立的词典数据库 reading_dict,其中收录了超过 14000 个词条(包含音标和释义),先精确匹配再前缀模糊匹配,最多返回 8 个结果。选中单词后显示音标、释义,并可通过 SpeechSynthesis API 朗读发音(0.85 倍速)。
阅读进度与计时
阅读器内置计时器,记录每次阅读的时长。进度每 3 秒自动保存一次(记录当前页、总页数、累计阅读时长),离开页面时通过 navigator.sendBeacon 做最后一次保存。当进度达到 95% 时,系统自动标记为"已读"。
成就系统
每次保存进度或录音后,后端会检查成就是否解锁。系统定义了 16 个成就,分布在四个类别:阅读量(读完 1/5/10/25/50 本)、连续天数(3/7/30 天)、累计时长(1/5/20/50 小时)、录音次数(1/10/50 次),以及一个"通关"成就(完成某个级别的所有绘本)。解锁新成就时,阅读器右上角会弹出带动画的 Toast 通知。
用户仪表盘
Dashboard 是用户的"主页",包含两个标签页:书架和我的统计。
书架
左侧是按分级的层级导航,右侧是书籍卡片网格,每张卡片展示封面、标题、星级评分、阅读进度条,以及"已读""录音"等标签。点击卡片弹出详情弹窗,显示封面大图、简介、页数、进度、评分组件,以及录音区——可以听自己的录音、重新录制、也可以听其他用户的录音。
书架上方还有一个"最近录音"动态流,展示全站最新的 5 条录音,附带用户头像、书名、级别、时长和播放控件,营造一种"学习社区"的氛围。
我的统计
六个统计卡片:已读完数、阅读中数、总阅读时长、录音次数、已读页数、本周时长。下方是最近阅读列表(最近 10 本)、最爱书籍(按阅读时长 Top 5)、各级别进度条,以及成就墙——已解锁的成就高亮显示并附解锁日期,未解锁的灰色展示。
公告展示
管理员发布的公告以轮播形式展示在 Dashboard 顶部信息栏中间,支持左右翻页和详情查看。这个设计让管理员可以灵活地向用户推送通知,而无需额外的消息系统。
管理后台
管理后台分为五个标签页:总览、书籍管理、上传、用户管理、公告管理。
总览页展示全局统计(总书籍数、用户数、已读完数)、各级别书籍分布、以及每个用户的阅读摘要表格,管理员可以一键查看某个用户的详细阅读报告。
书籍管理支持按级别筛选和标题搜索,可以编辑标题、简介、级别,以及切换封面显示模式(contain 完整显示 / cover 裁切填充)。
上传页前面已经介绍过,支持拖拽批量上传和自动转换。
用户管理可以新增、编辑、删除用户账号,重置密码,查看单个用户的完整阅读报告(包括分级进度、阅读记录明细、录音列表)。
公告管理支持创建、编辑、删除公告,以及切换启用/禁用状态。
技术实现的一些细节
单文件 API 设计
所有后端逻辑集中在一个 api.php 中,通过 action 参数路由。这种设计在小项目中非常实用:不需要路由框架,不需要 Composer,部署时只需确保 nginx 将请求转发到这一个文件。缺点是文件会越写越长(目前 1600 多行),但对于一个人维护的项目来说,找一个函数比找一个文件容易得多。
并发友好的图片服务
页面图片接口在返回数据前调用 session_write_close(),释放 session 锁,这样浏览器可以同时加载多张图片而不用排队等待。图片响应还设置了 1 周的缓存头,减少重复请求。
双数据库架构
主应用数据(用户、书籍、阅读记录等)和词典数据分别放在两个 MySQL 数据库中。这样做的好处是词典数据可以独立更新和备份,不影响主库。词典库中收录了 ECDICT 项目的约 14000 个词条。
数据模型
系统核心涉及以下数据表:
reading_systems 和 levels 构成分级体系;users 存储用户信息,区分 admin 和 reader 两种角色;books 是绘本主表,包含文件路径、页数、音频路径、封面设置等;reading_records 记录每个用户每本书的阅读进度;book_reviews 存储评分和笔记;book_recordings 存储录音文件路径和时长;achievements 和 user_achievements 构成成就系统;announcements 存储管理员公告。
所有用户相关的表都通过 user_id 外键关联,reading_records 对 (user_id, book_id) 设置了唯一约束,保证每个用户每本书只有一条进度记录。
总结与展望
这套系统目前运行稳定,核心功能——绘本上传、分级阅读、音频播放、录音跟读、查词、进度追踪、成就激励、公告通知——都已经可用。它证明了一件事:用传统的 LAMP 栈和原生前端,不依赖任何现代框架,也能做出功能完整、体验流畅的教育工具。
未来可以考虑的方向包括:增加拼读练习和阅读理解题目、支持移动端 PWA 离线阅读、引入更多分级体系的内容、以及优化图片加载策略(如 WebP 格式或懒加载)以进一步降低带宽消耗。
如果你也有类似的需求,希望这篇文章能给你一些启发。技术不一定需要多新,解决问题才是最重要的。