第一次,亲手把阅读这件事搭进代码里,始于2025年初。

一、从一个阅读习惯开始

为什么还要做“奇幻中文网”其实就是想要开发一个现代化的阅读平台。

说直白些,有两个原因。一是想练全栈。 教程刷了不少,但整条链路——页面、接口、数据库、部署、桌面端打包——我从来没自己完整走过一遍。我想真刀真枪做一件能上线的东西,而不是停在 localhost 的 demo。二是想做一个自己会打开的项目。 练手选题很多,我选阅读,因为它是我每天都会发生的事:我会真的往里加书、真的在里面读、真的会因为不顺手而改下一版。

所以,奇幻中文网不是因为「现成的工具不够好」,而是我想亲手搭一个,刚好落在了阅读这件事上

它是我第一个真正意义上的完整产品:浏览器里随时能打开的网站,加上一个适合长时间阅读的 Windows 桌面客户端。网站已经部署在公网(上上个月刚下线),客户端可以下载安装。说「从零到一」,指的是真的从零——从第一屏界面、第一张数据表、第一个能跑通的页面开始,慢慢长成一个能日常使用、但仍在持续修改的东西。

二、技术选型:选熟悉的,而不是选最酷的

一开始我就定了一个目标:Next.js + 数据库 + 桌面端,一条线打通。

前端用 Next.js,是因为它把页面和接口放在同一个项目里,对我这种第一次做全栈的人来说,心智负担小——不用前后端两个仓库来回切。TypeScript 是硬着头皮上的,前期慢,后期少踩很多类型相关的坑。样式用 Tailwind,写界面快;动效用 Framer Motion,页面切换和卡片交互能做出「顺」的感觉,而不是硬切。

数据库选了 PostgreSQL,用 Prisma 做 ORM。Prisma 的好处是模型和代码类型连在一起,改表结构有迹可循。用户、书籍元数据、书签、评论、阅读记录、IP 黑白名单——这些关系清晰的数据,放在关系型数据库里最自然。

桌面端用 Electron。不是因为它最新,而是因为我想做的是「一个真正的 Windows 应用」:无边框窗口、系统托盘、全局快捷键、本地缓存、自动更新——这些浏览器页面很难完整替代。

这套组合没什么猎奇,都是资料多、社区大的东西。处女作选熟悉的路,比选最酷的路更实际。我需要的不是炫技,是把链路跑通。

三、界面:OA 风格的阅读感,动效是润滑剂

做界面的时候,我没有往「炫」的方向走。

首页、书库、设置,整体偏 OA 风格——圆角卡片、留白够、信息层级清楚。暗色模式是标配,我几乎总是在晚上读,浅色底看久了眼睛累。

3.1 网页端-首页

微信图片_20260408160633_596_162.png
微信图片_20260408160640_597_162.png

3.2 网页端-书库

微信图片_20260408160658_599_162.png

3.3 网页端-书签

微信图片_20260408160708_600_162.png

3.4 网页端-设置

微信图片_20260408160735_601_162.png

3.5 网页端-阅读界面

微信图片_20260408160821_603_162.png

3.6 网页端-评论组件

微信图片_20260408160836_605_162.png

3.7 桌面客户端-首页

home.png

3.8 桌面客户端-书库

library.png

3.9 桌面客户端-书签

bookmarks.png

3.10 桌面客户端-设置

settings.png

3.11 桌面客户端-阅读界面

微信图片_20251119183205_40_181.png

3.12 桌面客户端-阅读设置

reader-settings.png

3.13 桌面客户端-评论组件

comments.png

动效上,页面入场、卡片 hover、轮播切换都加了轻微过渡。技术上就是 Framer Motion 的 stagger 和 tween,但目的很单纯:让我自己做的界面顺一点。第一版切换很生硬,我自己用着都别扭;后来加了轻微过渡,才稍微像样。

阅读器是整个项目里我投入最深的地方,代码量也最大——核心组件 alone 将近两千行,来来回回改了很多遍。

正文区域要沉浸,目录、设置、评论又不能一直占屏幕。我做了浮动导航,滚动时顶栏会藏起来;章节目录从侧边滑出;评论面板按需打开。字体、字号、行距、背景色都能调,设置存在本地,也同步到账号。这些都不是第一版就有的,是我读着自己做的阅读器,一处一处觉得「这里还碍事」改出来的。

四、做着做着,架构被迫变复杂了

4.1 最初的想法太简单

一开始就一个 Next.js 项目,连上 PostgreSQL,书籍表里有书名、封面、简介,正文先塞字段里,能读就行。

本地开发时完全够用。书少、数据小、机器快,一切仿佛理所当然。

4.2 书多了之后,问题来了

书越来越多、章节越来越长,书库列表开始变慢;有时候点进一本书,正文出来之前要等一小会儿。查下来才发现:「书的信息」和「书的正文」是两种完全不同的数据。

书籍元信息

章节正文

体积

大,单章几 KB 到几十 KB

访问频率

列表页每次都查

只有打开阅读器才需要

变更频率

相对高(人气、更新状态)

极低

把它们塞在同一张表、同一个库里,就像把图书馆索引卡和整本大部头放进同一个抽屉——能用,但越用越难翻。书库分页、搜索、排序,都会被大字段拖慢。

4.3 拆分:正文进 MongoDB,元信息留 PostgreSQL

那是我第一次被迫认真想架构。最后做了这样的分工:

  • PostgreSQL + Prisma:用户、书籍元数据、书签、评论、阅读会话、安全日志——结构化、要关联、要事务。

  • 独立的内容服务 + MongoDB:章节正文——大文本、读多写少、和列表查询无关。

主应用在启动时会拉起这个内容微服务。它跑在单独端口上,有自己的鉴权、限流和缓存。书库接口只查 PostgreSQL,返回书名封面简介;阅读器打开某一章,才去内容服务取正文。

这个决定不是一开始就设计好的,是写到一半踩坑之后才改。改动面很大,那段时间几乎都在填坑。处女作的好处是还改得起;代价是改起来真的累。

后来在 Prisma 的 Book 模型上补了很多索引——按人气排序、按更新时间、可见性加创建时间的复合索引——书库查询才稳下来。索引这件事我也是后来才学明白:不是上线之后再优化,而是模型设计时就要想到「用户会怎么翻」。

五、缓存:部署之后,才懂为什么需要它

架构拆完,本地又顺了。部署到服务器上,自己反复刷新书库、换账号、换浏览器试——然后又慢了。

个人服务器配置有限,书库、推荐、热门这些接口如果每次都从头查 PostgreSQL,几次刷新还行,用得多了就卡。那时候我才理解,缓存不是大厂的专利,小项目更经不起每一次都硬查库。

5.1 后端:内存 + Redis 两级

我在后端做了多级缓存:

请求进来 → 先查内存 → 没有再查 Redis → 还没有才查数据库

写入时 → 两级一起更新,带上过期时间

热点数据(书库列表、推荐、热门)命中缓存时,响应头里会标记是命中还是新查的,调试时能看出来。Redis 挂掉或没配的时候,会自动降级,直接走数据库——不至于整个站起不来。

接口层封装了一层通用的缓存中间件,同样的逻辑不用在每个 API 里重复写。书库、书籍详情、用户列表这些读多写少的接口,都挂了这层。

说实话,缓存这部分我一开始是抵触的,觉得项目还小何必呢。真正上线、自己反复用之后,才体会到差别——「能跑」和「用着不卡」之间,隔着这一层。

5.2 前端阅读器:按需加载 + LRU

后端解决了列表和推荐,阅读器还有自己的性能问题:一本长篇可能有上千章,不能一次全拉下来。

做法是章节懒加载——进阅读器先拿章节目录,点哪章再请求哪章的正文。读过的章节在前端用 LRU 策略缓存在内存里,最多保留五十章左右,避免无限涨;同时写一份到 localStorage,一小时内切回去不用重新等。

读连载长篇的时候,这种差别是感觉得到的——不是夸张到秒开,而是「不堵、不空等」。

六、账号、进度与实时:数据要跟人走

登录用的是 NextAuth,配合 JWT,方便 Web 和桌面端都能接。注册要走邮箱验证码,密码重置、改邮箱、安全日志——这些看起来「不像阅读核心功能」,但账号体系一旦有了,就得做完整,不然自己都不敢用。

阅读进度靠 ReadingSession 记录:什么时候开始读、读了多久、读到哪本书。书签支持分集合,带颜色和备注。评论可以嵌套回复,管理员能审核——后台藏在设置页里,和外观、隐私、安全放在一起,普通用户看不到,管理员日常够用。

另外还接了 Socket.IO,用于一些需要实时推送的场景。服务器没有用纯 Next 默认的启动方式,而是自定义了一个 Node 入口,把 Next、Socket、Redis 连接池、内容微服务的子进程、安全中间层放在一起管。这个入口文件后来也写得很长——功能都集中了,模块化还在演进中,这是处女作里很典型的状态。

七、桌面端:Electron 里那些「网页做不到」的事

网站能读之后,我又想要一个桌面版。

不是因为成熟的阅读 App 缺什么,而是我自己的使用场景:有一大块时间坐在电脑前,希望有一个常驻窗口、和网站共用同一套书与进度。做桌面端,也是全栈练手里「把客户端跑通」的那一环——无边框、托盘、快捷键、自动更新,这些在浏览器里不好完整练到。

桌面版用 Electron 打包,界面用 React 写,和网站视觉一脉相承,但多了这些东西:

无边框窗口。 标题栏自己画,最小化、最大化、关闭都在上面,看起来像原生应用而不是网页套壳。

全局快捷键。 在 Electron 主进程注册热键,比如 Ctrl+L 去书库Ctrl+B 看书签F1 看帮助;按键后通过 IPC 通知前端跳转。Preload 脚本里用白名单限制能监听哪些事件——繁琐,但是桌面应用安全的基本功。窗口缩到托盘后,快捷键还能把应用唤回来。

新手引导。 第一次打开,有几步 Spotlight 式的引导,指着侧边栏、统计、推荐、书签——不是说明书,就是告诉你东西在哪。

自动更新。 集成了 electron-updater,发新版本不用让人手动下 zip。

网站和桌面看起来是两个入口,背后连同一套 API。网页上读到第五章,打开客户端应该接着读——听起来理所当然,登录态同步、离线缓存、网络断开时的提示,都是桌面端单独要处理的。

有一个功能开了头没收尾:全局快速搜索,快捷键位留了,界面还没做完。处女作里这种「架子搭了、还没填满」的地方不少,如实说,不包装成已完成。

八、上线公网:安全这一课

产品部署到公网之后,日志里开始出现扫描、探测、来历不明的请求。以前只在 localhost 跑,从不需要想这些。

我在主服务入口加了一层简单的威胁评分:根据 URL 路径、查询参数、User-Agent 匹配可疑模式,累加分数;分数低的记录,高的观察,更高的临时封禁;配合数据库里的 IP 黑白名单,管理后台可以查看和干预。

这不是企业级 WAF,但对个人站点常见的噪音够用。感受和做缓存类似:「上线」是一条分水岭。上线之前想的是功能;上线之后还要想谁会来、来了会怎样。处女作让我第一次认真跨过这条线。

九、它用起来是什么样

撇开技术,从一个普通读者的角度,奇幻中文网大概是这样:

打开网站,首页有最近在看的书、今日推荐、热门和最近更新。进书库,能搜索、浏览,看到每本读到哪了。点进一本书,字号、行距、背景色、字体都能调,暗色模式晚上读舒服。好的段落加书签,可以和书友留评论。登录之后,状态跟着账号走。

下载 Windows 客户端,界面风格一致,但更适合长时间读——无边框、快捷键、托盘、新手引导,都是为「坐在电脑前读」服务的。

网站里有「关于」页,介绍客户端,也规划了一组截图:首页、书库、阅读器、目录、阅读设置、书签、评论、设置。写这篇文章时截图还没全部拍齐,发之前要补。真实界面比任何文字都有说服力。

十、体量与诚实

回过头看,这个项目比一开始想象的大:

  • 前端十多个页面,后台九十多个接口

  • 阅读器 alone 近两千行

  • 桌面端做了两个版本,对外发布的是精简版,产品名「奇幻中文网」

  • 技术栈大致是:Next.js、React、TypeScript、Tailwind、Framer Motion、PostgreSQL、Prisma、MongoDB、Redis、NextAuth、Electron、Socket.IO

这些数字不代表多厉害,只说明第一次做完整产品,体量会自然膨胀。

做对了什么?真的做完了,不是停在 demo;有暗色模式、进度、账号、管理后台、客户端下载页,像产品而不像作业;元信息和正文分开、接口做缓存、Web 和桌面共用后端——这几处关键决定,都是踩坑之后改出来的。

还不够什么?体验上,和成熟的阅读平台比,仍有明显差距——交互细节、内容生态、稳定性,我都还在学。代码层面,有些文件过长,阅读器和主服务入口尤其明显;网站和两套桌面界面有重复维护;测试几乎没系统做;全局搜索、部分截图、一些细节还是半成品。

我不觉得是失败。第一次的重点是跑通、交付、能用;精简和优雅,留给了下一次。

十一、结语

奇幻中文网不是什么颠覆行业的东西,更谈不上和那些成熟的阅读产品比体验。

我只是一个在读小说的人,刚好想借这件事,把全栈和桌面端走通一遍。用 Next.js 把 Web 和 API 写在一起,用 PostgreSQL 管关系和数据,把章节正文拆到 MongoDB 不让书库被拖慢,用 Redis 和内存缓存让常用页面不卡,用 Electron 做桌面端把快捷键和托盘这些环节练到手,上线之后留一道基本的威胁过滤。

它不是先有了蓝图再动工的。更像用着用着,才知道下一刀该落在哪——书多了,拆正文;上线了,加缓存;要长时间读,做桌面;挂公网了,留安全。