每年毕设开题季,Web方向的小说阅读网站几乎都会出现在选题列表里。用ThinkPHP5做一个完整的小说站,听起来门槛不高,但真要把前台阅读体验、后台管理、书架同步、搜索这些模块串起来跑通,还能在答辩时把每一步设计理由讲清楚,很多人是在最后两周才开始补课的。这篇就基于"thinkphp5基于web的小说阅读网站的设计与实现"这个题目,聊聊我做完这个项目后的完整思路:从选型逻辑、数据库设计、核心模块实现,到真实踩过的坑和部署答辩经验,一次说透。
这个题目的受众很明确——正在准备计算机毕业设计的同学,尤其是选了Web开发方向、用PHP框架做项目的。哪怕你还没开始写代码,只要把下面几条主线捋清楚,再动手做项目,至少能少走一半弯路。
1. 为什么小说阅读网站是毕设常青树,选ThinkPHP5是稳棋还是险棋
1.1 选题热度背后的真实原因
小说阅读网站这类题目在毕业设计里长期热门,原因有三个方面。
第一,需求边界清晰。用户端无非就是注册登录、分类浏览、搜索小说、查看章节、加入书架、记录阅读进度;管理端就是书籍管理、章节管理、分类管理、用户管理、数据统计。这种边界明确的业务非常适合用来展示"需求分析→数据库设计→编码实现→测试部署"的完整工程流程,答辩时有话可讲。
第二,技术栈覆盖全面。一个小说站天然涉及前端页面展示、后端接口逻辑、数据库设计、会话管理、全文搜索、缓存策略、权限控制等多种技术点。用ThinkPHP5实现时,框架的MVC分层、ORM、模板引擎、验证器、缓存机制基本都被用到,正好覆盖了课程里学的主要内容。答辩时无论老师问MVC还是问数据库,都能找到对应的落地点。
第三,业务可扩展性强。可以把"小说阅读"进一步扩展到评论系统、打赏功能、阅读排行榜、推荐算法等方向,这些扩展点让毕设的"创新性"有了着落,不至于做完一个纯增删改查项目后被质疑工作量不足。
1.2 为什么考虑用ThinkPHP5而不是其他框架
我帮人看过不少同类毕设,有人用ThinkPHP6,有人用Laravel,也有人坚持原生PHP。如果题目明确写了ThinkPHP5,那没什么好纠结的,按题目去做。但如果你还有自主选择空间,我的建议是:优先考虑ThinkPHP5。
原因很实际。学校机房、老一点的服务器环境、导师给的参考代码,大多围绕TP5编写。TP5的资料存量极大,遇到问题随便一搜就是解决方案,不像TP6那样很多教程还没跟上。对毕设项目来说,稳定、熟悉、资料多比"框架新"重要得多。
TP5本身的设计也比较适合教学展示。它保留了比较明显的MVC分目录结构,application/index/controller、application/admin/controller一眼就能看出哪个是前台哪个是后台;数据库操作既能走Db类原生查询,也能走模型关联;模板语法接近原生PHP,改起来不费劲。这些特点让答辩时讲技术细节更容易,因为框架没有帮你隐藏太多东西。
1.3 这个题目最容易翻车的三个地方
选了这个题不代表就能顺利做完。从我接触的情况看,最常见的翻车点有三个。
第一个是数据库设计想得太简单。小说站可不是两张表就能搞定的,至少要有用户表、书籍表、章节表、分类表、书架表、管理员表,外加可能的评论表、推荐位配置表。字段设计不合理的话,后面写功能时处处别扭。比如书架表如果没有处理好"书ID+章节ID"的联动更新,就很容易出现阅读进度丢失之类的问题。
第二个是阅读页性能没考虑。小说章节内容动辄几千字,如果把整本书的所有章节一次性查出来渲染,或者每访问一章都全表扫描一次,慢是必然的。这个问题后面会专门展开。
第三个是后台管理"重量"不够。有些同学把精力全砸在前台页面上,后台就放两个列表页草草了事。答辩时老师最爱问的就是"管理员怎么维护书籍内容""你怎么统计访问量",答不上来,基本就暴露了工作量不足。
2. 功能清单先排雷:前台、后台和隐藏加分项
2.1 前台用户端:不要只盯着"能看书"
前台是整个项目的门面,但很多人的理解仅限于"展示书籍列表和章节内容"。实际拆解下来,前台至少应该包含以下模块:
- 用户模块:注册、登录、退出、密码修改。注册时建议加入邮箱验证或至少验证码,手机号可以不做,避免学生身份的信息安全问题。登录态建议用TP5自带的Session记录,不需要引入JWT,降低复杂度。
- 书籍展示模块:首页推荐位、分类列表、书籍详情页。详情页要有书籍封面、作者、简介、最新章节信息、点击量和推荐按钮。这里的推荐位不要写死,建议在后台用推荐状态字段控制。
- 阅读模块:章节列表、正文阅读页、上一章/下一章切换、记忆上次阅读位置。正文页要做基本的排版处理,比如段落缩进、文字大小调节(加分项)、背景色切换(加分项)。
- 书架模块:加入书架、移出书架、按最近阅读时间排序。书架在登录状态下存库,未登录时给提示即可,没必要做本地缓存,否则数据同步逻辑会很麻烦。
- 搜索模块:按书名、作者名模糊搜索,搜索结果的排序和分页。这一块也是后文会提到的性能重灾区。
2.2 后台管理端:工作量撑起来的关键
有的同学觉得后台只是附赠,随便弄两下就行,这就错了。小说站的管理端恰恰是工作量最容易体现的地方。
后台建议独立跑一个admin模块,功能包括:
- 管理员登录与权限区分:至少区分超级管理员和普通编辑,普通编辑只能维护书籍内容,不能管理用户,更不能动系统设置。
- 书籍管理:书籍的增删改查、上下架、推荐位开关、封面上传。封面上传要考虑图片格式和白名单校验,不要随便拿一个
move()就完事。 - 章节管理:在书籍详情下按章节号维护章节内容,支持手动添加、批量导入、编辑删除。批量导入建议支持TXT文本按行拆分,这样答辩演示时导入一本真书的效果会非常震撼。
- 分类管理:分类的增删改和排序,分类删除前要检查是否有书籍关联。
- 用户管理:用户列表、禁用/启用账号、重置密码。
- 数据统计:书籍点击量排行、用户注册量走势、书架收藏排行,用简单图表展示即可。这里如果做得好,答辩时非常加分。
2.3 非功能性需求才是拉开差距的地方
除了前后的功能清单,我在帮人审方案时一定会强调三类非功能性需求。
第一是安全性。TP5的ORM自带参数绑定,但有些人图省事直接拼SQL,这就是漏洞。密码不能用明文存储,至少用TP5自带的md5加盐或password_hash。文件上传要限制类型,防文件包含攻击。
第二是健壮性。同一本书重复加入书架怎么办?后台删除书籍时关联的书架数据怎么处理?搜索关键词为空时是提示还是返回全部?这些问题如果都能在编码前想清楚,后面的调试时间能省一大半。
第三是可维护性。代码注释、目录规范、配置文件分离,这些本来就该做。答辩时老师翻代码看到注释规范,第一印象会好很多。
3. 数据库设计:小说站的核心竞争力其实在表结构
3.1 核心表结构与字段设计的思路
数据库表可以按"用户相关、内容相关、行为相关"三类来规划。内容相关是核心,行为相关最容易出错。
我最终采用的表结构大致如下(去掉了部分冗余字段,只讲主干):
-- 用户表 CREATE TABLE `novel_user` ( `id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, `username` VARCHAR(50) NOT NULL UNIQUE, `password` VARCHAR(255) NOT NULL, `email` VARCHAR(100) DEFAULT '', `create_time` INT(11) NOT NULL, `status` TINYINT(1) DEFAULT 1 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 分类表 CREATE TABLE `novel_category` ( `id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, `name` VARCHAR(50) NOT NULL, `sort_order` INT DEFAULT 0 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 书籍表 CREATE TABLE `novel_book` ( `id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, `category_id` INT UNSIGNED NOT NULL, `title` VARCHAR(200) NOT NULL, `author` VARCHAR(50) NOT NULL, `intro` TEXT, `cover` VARCHAR(255) DEFAULT '', `status` TINYINT(1) DEFAULT 1 COMMENT '1上架 0下架', `is_hot` TINYINT(1) DEFAULT 0 COMMENT '首页推荐', `click_count` INT UNSIGNED DEFAULT 0, `chapter_count` INT UNSIGNED DEFAULT 0, `last_chapter_title` VARCHAR(200) DEFAULT '', `create_time` INT(11) NOT NULL, KEY `category_id` (`category_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 章节表 CREATE TABLE `novel_chapter` ( `id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, `book_id` INT UNSIGNED NOT NULL, `chapter_no` INT UNSIGNED NOT NULL COMMENT '章节序号', `title` VARCHAR(200) NOT NULL, `content` MEDIUMTEXT NOT NULL, `word_count` INT UNSIGNED DEFAULT 0, `create_time` INT(11) NOT NULL, UNIQUE KEY `uk_book_no` (`book_id`, `chapter_no`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 书架表 CREATE TABLE `novel_shelf` ( `id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, `user_id` INT UNSIGNED NOT NULL, `book_id` INT UNSIGNED NOT NULL, `chapter_id` INT UNSIGNED NOT NULL COMMENT '最近阅读章节', `update_time` INT(11) NOT NULL, UNIQUE KEY `uk_user_book` (`user_id`, `book_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;特别注意novel_chapter的唯一索引uk_book_no,它的作用是防止同一本书下章节号重复。小说章节一旦通过导入或爬虫维护,很容易出现重复章节,没有唯一索引的话,后台插入第二条同号章节时就会静默覆盖或报错,排查起来非常痛苦。
3.2 设计时最容易疏忽的两个关联细节
第一个细节是书籍表和章节表之间的冗余字段。我在novel_book里放了chapter_count和last_chapter_title,创建章节时同步更新这两个字段。冗余字段会增加维护逻辑,但换来的好处是首页和书籍列表页不用每次count和查最新章节,性能提升明显。代价是每次增删章节时要记得同步更新,忘记更新就会显示错误。这里可以在章节模型的事件钩子里统一处理,避免散落在多个控制器里。
第二个细节是书架表的唯一索引和更新策略。书架表用的是UNIQUE KEY uk_user_book (user_id, book_id),保证一个用户对同一本书只保留一条记录。当用户读到新章节时,不是新增一条记录,而是更新chapter_id和update_time。这个"插入时若存在则更新"的操作,用TP5的Db::execute配合ON DUPLICATE KEY UPDATE一步完成,比先查后改要靠谱得多,还避免了并发下的数据不一致。
3.3 编码前的ER图和数据字典
在动手写代码之前,务必用工具把ER图画出来,再写一份数据字典。数据字典的格式可以参考这样的表格,答辩时可以放到论文附录里:
| 表名 | 主要字段 | 字段类型 | 说明 |
|---|---|---|---|
| novel_user | id | INT | 主键 |
| novel_user | username | VARCHAR(50) | 用户名,唯一 |
| novel_book | title | VARCHAR(200) | 书名 |
| novel_book | status | TINYINT(1) | 1上架 0下架 |
| novel_chapter | content | MEDIUMTEXT | 章节正文 |
| novel_shelf | chapter_id | INT | 最近阅读章节ID |
数据字典不仅方便编码时查字段名,也是论文和答辩PPT里必不可少的内容。提前花一小时整理好,后面省下的是反复翻表结构的时间。
4. 四大核心模块的实现套路:书架、阅读、搜索、用户体系
4.1 书架模块:自动更新进度的逻辑
书架看起来简单,但"自动更新进度"这个交互要做好是有讲究的。用户不需要手动点击"更新进度",而是在打开正文页时自动把当前章节写进书架。
我当时的实现是在阅读页控制器里统一处理:
public function read() { $bookId = (int)input('param.book_id'); $chapterId = (int)input('param.id'); $chapter = Db::name('chapter')->where('id', $chapterId)->where('book_id', $bookId)->find(); if (!$chapter) { $this->error('章节不存在'); } // 更新阅读进度 $userId = session('user_id'); if ($userId) { $data = [ 'user_id' => $userId, 'book_id' => $bookId, 'chapter_id' => $chapterId, 'update_time'=> time(), ]; Db::execute("INSERT INTO novel_shelf (user_id, book_id, chapter_id, update_time) VALUES (:user_id, :book_id, :chapter_id, :update_time) ON DUPLICATE KEY UPDATE chapter_id = VALUES(chapter_id), update_time = VALUES(update_time)", $data); } // 继续渲染阅读页 ... }这样每次进阅读页时,书架进度自动就是最新的。SQL语句里用了命名占位符而不是字符串拼接,这点在TP5里尤其重要,能防SQL注入。
书籍详情页的"加入书架"按钮也不要做成单独的表单请求,直接用AJAX调用一个接口,返回JSON状态,前端提示"已加入书架"或"已在书架中"。接口内部同样处理插入和更新的情况。
4.2 阅读页:章节切换与性能
阅读页是用户停留时间最长的页面,这部分优化做得好,展示效果立竿见影。
章节列表建议在书籍详情页异步加载,而不是直接渲染几十章甚至几百章。可以用TP5的paginate()做分页,每次加载50章,滚动到底部再请求下一页。这里用AJAX加HTML拼接的方式,后端返回渲染好的片段,前端直接插入DOM,比返回JSON再拼DOM简单得多。
正文渲染有一个关键细节:小说的章节内容通常来自TXT导入或收集的数据,里面会有大量换行、半角空格和各种分隔符。入库前一定要做数据清洗。我常用的做法是入库时统一把\r\n转换成<br>,再过滤掉HTML标签,防止XSS。展示时用CSS统一控制段落间距,而不是直接把原始的text/plain输出到页面上。
上一章/下一章切换可以走同一个阅读控制器,用chapter_no加减1来判断:
// 上一章 Db::name('chapter') ->where('book_id', $bookId) ->where('chapter_no', '<', $currentNo) ->order('chapter_no desc') ->find(); // 下一章 Db::name('chapter') ->where('book_id', $bookId) ->where('chapter_no', '>', $currentNo) ->order('chapter_no asc') ->find();注意这里要用章节号而不是主键id来判断相邻章节,因为手动导入时主键顺序未必和章节顺序一致。
4.3 搜索模块:模糊查询之外的性能优化
搜索是小说站的标配功能,也是很多毕设做得最粗糙的地方。最简单的实现是:
$keyword = input('keyword'); $list = Db::name('book') ->where('title|author', 'like', '%'.$keyword.'%') ->paginate(10);这个写法在数据量小的时候没问题,一旦书籍表超过几千条且没有索引,LIKE '%关键词%'就会做全表扫描,搜索响应会明显变慢。优化可以从三个层面入手。
第一个层面是加索引。title和author字段各自建普通索引,虽然LIKE '%keyword%'不一定走索引,但MySQL 5.7以上对字符串长度较短的字段在一定条件下也能用上索引,至少可以缓解。第二个层面是加缓存。用TP5的Cache类把搜索结果缓存起来,缓存键用关键词的MD5值,过期时间设5分钟。第三个层面是限制搜索范围。前端加一个下拉选项,让用户选择"按书名"或"按作者"搜索,避免两个字段同时模糊匹配带来的性能开销。
还有一个小建议:搜索页不要返回空页面给用户。搜不到结果时,给出"推荐阅读"栏目,拉几条热门书籍填充,这样既友好又能提升页面利用率。
4.4 用户体系:注册、登录和密码处理
用户体系是小说站的基础功能,TP5做起来很快,但有几个细节不能马虎。
注册时要用TP5的Validate类做表单验证,用户名、邮箱格式、密码长度都有对应规则。不要用前端JS验证就算完,服务端验证必须做。密码存储我用的是password_hash()生成,校验时用password_verify(),这和TP5自带的md5封装不同,但更安全,答辩时如果老师问到密码存储策略,这是一个很不错的加分回答。
登录后的用户状态用session('user_id')记录,模板中用session('username')判断是否显示用户名还是"登录/注册"按钮。注销时调用session(null)清空所有会话数据,防止用户信息残留。
5. 性能优化和缓存策略:让老框架跑出体面速度
5.1 缓存策略应该从哪个层面做
小说站的性能瓶颈不在框架本身,而在数据库查询和页面渲染。TP5自带的缓存机制支持文件缓存和Redis缓存,没有Redis环境时可以直接用文件缓存,代码写起来一样,答辩时说明"文件缓存独立部署"也是一种合理方案。
我的推荐是两级缓存。第一级缓存书籍基础信息和章节列表,键名类似book_info_{id}、book_chapter_list_{id},过期时间600秒。第二级缓存正文内容,键名chapter_content_{id},过期时间可以设置更长一些,因为章节内容入库后基本不会频繁修改。这样连续点击阅读多个章节时,大部分查询都走缓存,数据库压力一下子下来了。
代码示例:
use think\Cache; // 获取章节内容,优先走缓存 function getChapterContent($chapterId) { $cacheKey = 'chapter_content_' . $chapterId; $content = Cache::get($cacheKey); if (!$content) { $content = Db::name('chapter')->where('id', $chapterId)->value('content'); Cache::set($cacheKey, $content, 3600); } return $content; }注意value('content')返回的是字段值而不是整行记录,比find()后取字段要省一点内存。
5.2 SQL层面的优化和慢查询排查
性能问题光靠缓存不能全解决,SQL本身也要写对。比如书籍列表按点击量排序时,如果不对click_count字段建索引,数据量大之后排序会非常慢。后台统计用户注册走势时,不要用GROUP BY去扫全年数据,可以按天生成一条统计记录,后台直接读统计表。
调试SQL性能时,TP5提供了Db::getLastSql()方法,可以在排查时打印最后执行的SQL语句。结合数据库管理工具的慢查询日志,基本能精准定位是哪条查询拖慢了响应。我在实测中发现,很多"页面加载很慢"的问题不是单条SQL太慢,而是循环里调用了查询。典型的场景是:书籍列表页循环每本书查最新章节,N本书就是N条SQL。后来改成在列表页直接用join关联章节表按MAX(chapter_no)分组查询,一次SQL就把所有书籍的最新章节信息带出来。
不要把所有逻辑都放在控制器里,TP5的模型层有事件机制和访问器,把复杂查询封装成模型方法,控制器只负责调度,这样代码结构清晰得多,排查性能问题也更方便。
5.3 前端层面的加载优化
除了后端,前端也有几个简单有效的优化点。
封面图是小说站最占体积的资源之一。封面图压缩到200KB以内,必要时用懒加载,loading="lazy"属性在详情页列表场景下很有用。首页推荐位不要放太多书,8到10本足够,否则页面长度直接拉跨,用户还没看到分类导航就失去耐心了。
正文页的上一章/下一章按钮建议提前预加载。当前章节渲染完成后,用AJAX静默请求下一章节的内容,存入浏览器缓存或一个隐藏DOM节点。用户点"下一章"时几乎无缝切换,阅读体验提升明显。这个功能记得提前测试,防止预加载和实际点击时章节号不一致造成的错乱。
6. 实测中遇到的三个坑:白屏、错乱和超时的完整排查
6.1 首页白屏:伪静态配置和运行目录权限
第一次在服务器上部署TP5项目,最常见的问题是一打开首页就404或者白屏。当时我排查了一整天,最后定位到两个原因。
第一个是Nginx的伪静态规则没有按TP5文档配置。TP5的URL访问依赖pathinfo模式,如果服务器没有把请求重写到index.php,路由就无法解析。正确的Nginx配置是在server块中加入:
location / { if (!-e $request_filename) { rewrite ^(.*)$ /index.php?s=$1 last; } }第二个是runtime目录没有写权限。TP5运行时会向runtime目录写入缓存文件和日志,如果没有写权限,页面会直接抛出系统错误甚至白屏。部署时执行一次:
chmod -R 777 runtime顺便提一句,本地开发时用TP5自带的php think run命令启动开发服务器,伪静态问题会少很多,但部署到Nginx时还是要回归检查一遍。
6.2 书架章节进度错乱的根因排查
有段时间书架功能出现一个很隐蔽的bug:用户读到了第80章,但书架列表显示的进度还是第45章,而且刷新后进度还会跳变。一开始我以为是写入时机不对,后来加了日志才定位到真正的原因。
问题出在书架写入的SQL上。当时我先用select查一遍书架里有没有这本书的记录,有就update,没有就insert。但用户连续点击"上一章""下一章"时,两次请求几乎同时到达,两个请求都查到不存在记录,于是都走insert分支,其中一个插入失败被异常吞掉,整体进度就没更新。改成INSERT ... ON DUPLICATE KEY UPDATE后,这个问题就彻底消失了。所以这里也想提醒一句:涉及"存在即更新、不存在即插入"的场景,不要让数据库的查询和写入分两步走,直接用原子化操作。
6.3 搜索超时:模糊查询字段的索引问题
搜索页在书本数量达到几千条之后开始变慢,有些关键词甚至需要两三秒才能出结果。我查看数据库慢查询日志,发现慢的不是搜索结果本身,而是"搜索结果的统计总数"那条SQL。
TP5的paginate()默认会先执行一次count查询,而count加LIKE模糊查询的组合非常消耗性能。解决方式有两个:如果翻页不需要精确总数,就改用Paginator的简化模式,或者在前端直接限制最大翻页数,比如最多50页,超出就提示用户缩小关键词范围。另一个方式是给title、author字段各加一个普通索引,虽然对%keyword%帮助有限,但至少能让MySQL在排序时快一点。
要提醒的是,不要为了搜索性能轻易引入全文索引或Elasticsearch。对毕设项目来说,数据量根本没有到那个量级,加了复杂组件会引入大量额外运维成本,答辩时还可能被追问"你是怎么保证索引和数据库一致性的",反而给自己挖坑。
6.4 数据导入:中文乱码和章节错乱
书籍数据导入环节也踩过不少坑。从网上拿来的TXT文件,编码很可能是GBK或GB2312,直接入库就会变成乱码。必须在导入前统一转成UTF-8:
$content = mb_convert_encoding($content, 'UTF-8', 'GBK');如果混合了多种编码,建议先做编码检测,再统一转换。导入章节时还要注意目录识别的逻辑,有些TXT文件开头有"第一章 XXXXX"的目录行,正文里又有同样的标题行,不做过滤就会产生大量空章节或重复章节。
我当时的方法是先按行读取,用正则匹配第([0-9零一二三四五六七八九十百千]+)章识别章节标题,命中标题行就新开一个章节,正文行累积到当前章节里。标题行和正文行都有的情况下,优先保证章节号递增且唯一,重复标题直接跳过。
7. 部署到服务器和答辩准备的最后冲刺
7.1 服务器部署的环境细节
部署环境我建议采用最常见搭配:Nginx + PHP 7.x + MySQL 5.7。操作系统选Linux发行版。具体安装流程网上很多,不再赘述,但有几个容易踩的细节值得强调。
一是PHP版本的选择。TP5.0在PHP 7.0到7.4下表现稳定,不建议用PHP 8,部分老扩展可能不兼容,报错排查起来麻烦。二是OPcache建议开启,PHP进程对TP5这种多文件的框架加速效果明显。三是application/config.php里的app_debug,部署后要设为false,app_trace也要关掉,否则页面底部会显示调试信息,安全性也不好。
数据库连接时,application/database.php里的hostname不要用localhost,优先用127.0.0.1,避免PHP在某些环境下解析localhost为IPv6导致连不上。字符集设置成utf8mb4,因为章节内容里很可能有各种特殊字符和emoji,utf8会丢数据。
7.2 演示数据怎么准备才像样
答辩演示时最尴尬的事情,莫过于页面上只有零星几本测试书,章节内容还是"aaaabbbb"这种假数据。建议提前准备一套像样的演示数据。
至少找5到8本不同分类的书籍,每本书配好封面图、简介、统一风格的章节内容。可以手动整理几本短篇,每本录入10到20章,确保章节列表、阅读切换、书架进度这些功能都有内容可展示。更讲究一点的话,模拟一些真实的用户行为数据:注册几个演示账号,让其中一个账号的书籍点击量、书架收藏量看起来自然一些,后台统计页面才有内容可看。
封面图不要用太大的图,建议提前用工具压缩,并统一尺寸。上传到本地服务器时记得把图片放到public/static/upload目录下,不要放在runtime里,否则清理缓存时图片会被误删。
7.3 答辩提问的高频问题清单
答辩问答环节,老师基本会围绕以下几个方向提问,提前准备答案很重要。
第一个方向是框架原理。ThinkPHP5的请求生命周期是怎样的?$_GET和TP5的Input类有什么异同?路由是怎么实现伪静态的?这类问题只要画一遍流程图基本都能答上来。
第二个方向是数据库设计。为什么书架表要设唯一索引?书籍表和章节表之间是什么关系?冗余字段怎么保证一致性?这些问题在本文第3章都有对应解释。
第三个方向是安全。怎么做SQL注入防护?密码加密是怎么做的?文件上传有哪些限制?如果你在实现里用了预处理、密码哈希、白名单校验,如实回答即可。
第四个方向是性能。缓存用的是什么机制?大数据量下搜索怎么优化?有没有考虑过并发访问?这类问题不求答得多深,关键在于展示出你做过的优化决策。
建议答辩前把项目的核心流程图和数据库ER图打印出来或者放进PPT。演示时不要一上来就登录后台,按用户视角走一遍:首页、分类列表、书籍详情、阅读、书架、搜索,再做一遍后台的书籍和章节管理,最后展示统计页。流程顺畅,比花里胡哨的界面更能赢得老师好感。
这些做完,这个题目差不多就稳了。最后分享一个我做毕设时的体会:这类项目真正的分水岭不在"能不能跑起来",而在"能不能讲清楚为什么这么设计"。代码写完只是第一步,把每个模块的设计理由、每次排查问题的思路都整理清楚,答辩时你才有底气。如果时间有余,把书架自动更新、搜索缓存优化这两个点再深挖一下,做成"设计亮点"写进论文里,效果会更好。