简介:疯子CMS-轻简小说系统是一套面向小说网站运营者的内容管理系统,主打简洁易用,适合缺乏技术背景的个人站长快速搭建并管理自己的小说平台。压缩包共884个文件,约5.57MB,以334个php核心程序、138个html页面、132个js脚本和29个css样式为主,另含gif、png、jpg等图片素材及字体、图标资源,并附带update_sql.sql数据库脚本与configs配置目录,覆盖内容管理、权限验证、主题模板等模块。系统内置chinese-conversion中文简繁转换工具,便于两岸三地用户阅读,themes目录支持多套界面风格切换,lib核心库承载用户认证与内容检索逻辑。目前已有331人学习下载,读者可借此研究小说CMS的目录结构、数据库设计与前后端组织方式,快速搭建具备分类、评论、搜索与付费阅读扩展能力的小说站点。
1. 小说CMS选型:为什么我最终拆了这套轻简系统
去年帮一个做网文的朋友看站点,他花大价钱买的商业小说CMS,光后台菜单就三层,编辑一章要跳四个页面,服务器跑起来内存直接吃掉 2G。后来我拿到这套疯子CMS-轻简小说系统,解压一看目录结构就明白了——它走的是另一条路:把小说站最核心的「书-卷-章」三层结构做薄,前端模板拆成独立 CSS,后台只保留必要功能。这套系统适合两类人:一是想快速搭个小说站验证运营思路的个人站长,二是不想被重型框架绑架、愿意自己改模板的前端。它不解决高并发和复杂推荐,但能把「上传小说、分卷、换皮肤、简繁转换」这条链路跑通,而且代码量小到你能在一周内读完核心逻辑。下面我按实际拆包顺序,把目录结构、数据库、模板机制和几个容易翻车的地方讲清楚。
2. 目录结构与核心文件:从 layui.css 到 update_sql.sql 的拆解
2.1 前端资源分层:为什么 CSS 被拆成这么多文件
解压后第一眼看到的就是一堆 CSS 文件名:layui.css、recentread.css、style.css、mip.css、admin.css、reader.css、bqg.css。这不是作者随意堆的,而是按「加载场景」拆的。layui.css是后台管理界面的基础框架样式,admin.css是后台的定制覆盖;前台则拆成style.css(通用布局)、recentread.css(最近阅读模块)、reader.css(阅读页正文排版)、bqg.css(可能是某个主题的配色方案,bqg 通常指「笔趣阁」类配色)、mip.css(针对 MIP 移动加速页的样式)。这种拆法的好处是:阅读页只加载reader.css和bqg.css,不用把后台的 layui 拖下来,首屏能省 100KB 左右的 CSS 请求。
我一般会先看www目录下的入口文件,确认模板里是怎么引用这些 CSS 的。常见做法是在模板头部用条件判断:
<!-- 阅读页只加载阅读相关样式 --> {if $action == 'reader'} <link rel="stylesheet" href="__STATIC__/css/reader.css"> <link rel="stylesheet" href="__STATIC__/css/bqg.css"> {else} <link rel="stylesheet" href="__STATIC__/css/style.css"> {/if}这段是模板引擎的条件加载逻辑,$action是控制器传入的当前动作变量。如果你发现阅读页样式错乱,先检查这个判断有没有生效,而不是去改reader.css本身。参数上,__STATIC__是模板常量,通常指向/static目录,换服务器时如果路径不对,所有 CSS 都会 404。
2.2 数据库初始化:update_sql.sql 里有什么
update_sql.sql是建库脚本,直接导入就能生成表结构。我习惯先看表名和字段,判断这套系统的数据模型是否合理。小说 CMS 的核心表一般包括:novel(小说主表)、chapter(章节表)、category(分类)、author(作者)、user(用户)、comment(评论)。疯子CMS 的章节表大概率用novel_id做外键,chapter_order做排序,正文存content字段(可能是 mediumtext 或 longtext)。
导入时注意字符集。如果update_sql.sql里写的是utf8而不是utf8mb4,遇到 emoji 或生僻字会报错。我一般会手动改成:
CREATE TABLE `novel_chapter` ( `id` int(11) NOT NULL AUTO_INCREMENT, `novel_id` int(11) NOT NULL, `title` varchar(255) NOT NULL, `content` longtext, `chapter_order` int(11) DEFAULT 0, `create_time` int(11) DEFAULT 0, PRIMARY KEY (`id`), KEY `novel_id` (`novel_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;这里novel_id加索引是必须的,否则小说章节多了以后,按书查章节会全表扫描。chapter_order用 int 而不是 varchar,方便排序。create_time存时间戳而不是 datetime,省空间且时区处理简单。导入前先建库:
mysql -u root -p -e "CREATE DATABASE novel_cms DEFAULT CHARSET utf8mb4;" mysql -u root -p novel_cms < update_sql.sql第一条命令建库并指定字符集,第二条导入表结构。如果导入报错「Unknown character set」,说明 SQL 文件里有utf8mb4_unicode_ci但 MySQL 版本低于 5.5,升级或改成utf8_general_ci。
2.3 configs 目录:数据库连接和站点参数怎么配
configs目录下通常有database.php和site.php。database.php里填主机、库名、用户名、密码、端口。我见过有人把localhost写成127.0.0.1反而更快,因为跳过 socket 解析。site.php里是站点名称、域名、静态资源路径、是否开启简繁转换等。
配置时容易忽略的是「伪静态」规则。小说站 URL 一般是/novel/123/和/chapter/123/456.html,需要在 Nginx 里加 rewrite:
location / { if (!-e $request_filename) { rewrite ^/novel/([0-9]+)/?$ /index.php?m=novel&id=$1 last; rewrite ^/chapter/([0-9]+)/([0-9]+)\.html$ /index.php?m=chapter&novel_id=$1&chapter_id=$2 last; } }这段规则把小说页和章节页映射到 index.php 的对应模块。last表示重写后继续匹配。如果没加,访问/novel/123/会 404。改完记得nginx -t测试再 reload。
3. 模板与主题机制:themes 目录怎么换、chinese-conversion 怎么用
3.1 themes 目录结构:换主题不是换个文件夹那么简单
themes下每个子目录是一套主题,里面通常有index.html、list.html、reader.html、header.html、footer.html。换主题时,后台一般有个「模板选择」下拉,选完会写进configs/site.php的theme字段。但实际渲染时,系统会先找themes/当前主题/下的文件,找不到再回退到默认主题。
我踩过的坑是:新主题的reader.html里用了旧主题没有的变量,比如{$chapter.novel_name},但控制器没传这个字段,页面就空白。排查方法是打开模板调试,或者直接在控制器里var_dump($data)看传了什么。常见做法是在lib目录的控制器基类里加一个assign方法,统一注入公共变量,比如站点名称、分类列表。
换主题后如果样式全丢,检查themes/新主题/下有没有css文件夹,以及模板里引用的路径是相对还是绝对。我一般改成__STATIC__/themes/xxx/css/style.css这种常量路径,避免相对路径在伪静态下解析错误。
3.2 chinese-conversion:简繁转换的接入点和性能取舍
chinese-conversion这个库是用来做简繁互转的。小说站面向不同地区用户时,同一本书可能需要输出简体或繁体。接入方式一般是在章节内容输出前调用转换函数:
// 在章节控制器里,输出前转换 require_once LIB_PATH . 'chinese-conversion/converter.php'; $converter = new ChineseConverter(); $content = $converter->convert($chapter['content'], 'zh-tw'); // 转为繁体 $this->assign('content', $content);convert方法第一个参数是原文,第二个是目标语言代码,zh-tw繁体、zh-cn简体。注意这个转换是逐字映射,不处理词汇差异(比如「软件」转成「軟體」而不是「軟件」),所以只适合阅读场景,不适合做 SEO 标题。性能上,一章 3000 字转换耗时约 5-10ms,如果每页都转,QPS 高时 CPU 会上去。我一般会在保存章节时就把简繁两个版本都存进数据库,用content_simple和content_traditional两个字段,读取时直接取,省掉运行时转换。
3.3 阅读页排版:reader.css 和 bqg.css 的配合
阅读页的体验全在reader.css和bqg.css。reader.css管字号、行高、段间距、背景色;bqg.css管配色方案(比如米黄底、深灰字)。常见做法是把配色做成 CSS 变量,方便后台切换:
:root { --reader-bg: #f5f0e1; --reader-color: #333; --reader-font-size: 18px; --reader-line-height: 1.8; } .reader-content { background: var(--reader-bg); color: var(--reader-color); font-size: var(--reader-font-size); line-height: var(--reader-line-height); }这样用户选「护眼模式」时,只需要改:root里的变量值,不用重写整个样式表。bqg.css里可以预设几套变量组合,通过切换 class 来生效。注意移动端要加-webkit-text-size-adjust: 100%,否则横屏时字号会乱。
4. 避坑与排查:导入 SQL、伪静态、模板缓存的血泪经验
4.1 导入 update_sql.sql 报错「Table already exists」
现象:导入时提示某张表已存在,脚本中断。原因:之前导入过一半,或者数据库里有同名表。解决:先删库重建,或者在 SQL 文件开头加DROP TABLE IF EXISTS。我一般会先执行DROP DATABASE novel_cms; CREATE DATABASE novel_cms;再导入,确保干净。如果表里有数据不想丢,用INSERT IGNORE或ON DUPLICATE KEY UPDATE改脚本。
4.2 伪静态规则不生效,访问 /novel/123/ 返回 404
现象:配置了 rewrite 但页面还是 404。原因:Nginx 的location /里没有try_files,或者 rewrite 规则写在了错误的位置。解决:确保index index.php存在,rewrite 规则放在location /内,并且if (!-e $request_filename)判断的是文件不存在才重写。另外检查configs/site.php里的url_mode是否设成了rewrite,有些系统需要后台开启伪静态开关。
4.3 换主题后页面空白,但源码里有内容
现象:浏览器显示空白,查看源码发现 HTML 被注释或截断。原因:模板里用了未定义的变量,PHP 报错但被display_errors关掉了。解决:临时在入口文件加ini_set('display_errors', 1); error_reporting(E_ALL);,刷新看报错信息。常见是{$novel.title}写成了{$novel.name},或者循环变量名不对。修完记得关掉错误显示,避免暴露路径。
4.4 章节内容里的 HTML 被转义,排版全乱
现象:后台存了带<p>的正文,前台显示成<p>。原因:模板输出用了{$content|htmlspecialchars}或者系统默认转义。解决:阅读页模板里改成{$content|raw}或{$content nofilter}(取决于模板引擎)。但要注意,如果允许用户投稿,直接输出 raw 会有 XSS 风险,建议在保存时用白名单过滤标签,只留<p>、<br>、<strong>。
4.5 简繁转换后搜索不到内容
现象:用户搜简体关键词,但数据库里存的是繁体,搜不到。原因:搜索时没有对关键词和内容做统一的简繁处理。解决:在搜索控制器里,先把关键词转成简体,再同时查content_simple和content_traditional两个字段,或者用 MySQL 的CONVERT函数。更简单的做法是只存简体,输出时按用户选择转换,搜索永远走简体字段。
5. 进阶技巧:用 lib 目录做二次开发与性能兜底
5.1 lib 目录的扩展点:从用户认证到内容检索
lib是核心库,里面一般有Auth.php(用户认证)、Novel.php(小说模型)、Chapter.php(章节模型)、Router.php(路由)。二次开发时,我建议不要直接改这些文件,而是新建lib/extends/目录,用继承或钩子覆盖。比如要加一个「章节购买」逻辑,可以在Chapter.php的getContent方法里加钩子:
// 在 lib/Chapter.php 的 getContent 方法里 public function getContent($chapterId, $userId = 0) { $content = $this->db->get('chapter', ['id' => $chapterId]); // 钩子:检查是否付费章节 $hookResult = Hook::call('chapter_before_output', $content, $userId); if ($hookResult === false) { return ['error' => '需要购买']; } return $content; }Hook::call是自定义的钩子调度器,chapter_before_output是钩子名,后面是参数。这样加付费逻辑不用动核心文件,升级时也不会冲突。参数上,$userId为 0 表示未登录,钩子里可以判断权限。
5.2 缓存策略:哪些页面该静态化,哪些该实时查
小说站的特点是「读多写少」,章节内容一旦发布几乎不变。我一般会把章节阅读页生成静态 HTML,放在www/html/下,Nginx 直接返回,不走 PHP。生成规则:
# 伪静态生成脚本示例 php cli.php make_html --novel_id=123 --chapter_id=456cli.php是命令行入口,make_html是任务名,后面是参数。生成的 HTML 文件路径按/html/123/456.html存,Nginx 加一条location /html/ { root /www/wwwroot/novel; }。这样单机跑几万 PV 没问题。但要注意,如果章节内容支持用户评论实时显示,静态页里的评论就得用 AJAX 拉,不能直接嵌。
5.3 数据库读写分离的轻量做法
如果站点流量上来,单库扛不住,可以在configs/database.php里配主从:
return [ 'master' => ['host' => '127.0.0.1', 'user' => 'root', 'pass' => 'xxx', 'db' => 'novel_cms'], 'slave' => [['host' => '127.0.0.1', 'user' => 'read', 'pass' => 'xxx', 'db' => 'novel_cms']], ];然后在lib/Db.php里判断:写操作走 master,读操作随机选一个 slave。小说站读多写少,一主一从就能撑很久。注意主从同步延迟,刚发布的新章节如果立刻读从库可能读不到,所以发布后的第一次读取强制走主库。
5.4 验证清单:上线前我必跑的五个检查
| 检查项 | 命令/操作 | 预期结果 |
|---|---|---|
| 数据库字符集 | SHOW VARIABLES LIKE 'character_set_database'; | utf8mb4 |
| 伪静态 | curl -I http://站点/novel/1/ | 200 |
| 模板缓存 | 删除runtime/cache/后刷新 | 页面正常 |
| 简繁转换 | 切换语言后看章节内容 | 正确转换 |
| 静态生成 | 访问/html/1/1.html | 返回 HTML |
上线前我习惯把runtime目录清空,确保没有旧缓存干扰。如果用了 CDN,记得刷新 CSS 和 JS 的缓存版本号。
从那以后我每次拿到新的小说 CMS,都先跑一遍这个清单,再动手改模板。希望帮到你。
本文还有配套的精品资源,点击获取