从标题发出去那一刻起评论区就比我预想的要热闹得多。有人问是不是拿爬虫扒的别人题库有人问服务器扛不扛得住还有人直接私信我“哥八股文网站这么多你这个有什么不一样”。说实话这些问题我在做之前都想过也正是因为市面上的面试题资源要么太散、要么太旧、要么看着就像十年前从某个论坛复制下来的我才决定自己动手搭一个“八股文小破站”。先说清楚这个小破站是什么一个面向程序员求职面试准备的个人学习站点主打技术面试里常考的基础知识题也就是俗称的“八股文”——计算机网络、操作系统、数据库、数据结构、Java或Go这类编程语言基础以及高频场景题。它解决的核心问题是把那些散落在各个文档、掘金文章、牛客帖子里面的面试考点整理成一个可以按系统刷、按标签筛、能记录错题的独立站点。适合正在准备校招、社招跳槽或者想系统过一遍计算机基础的开发者参考。这个小破站从想法到上线前后大概花了一个多月的时间踩了不少坑也删掉过两个版本。这篇文章我打算把整个项目从头拆一遍不是发个上线公告就完事而是把技术选型、功能设计、部署上线、常见问题这些环节全部摆出来给想自己做个人站点的朋友一个能直接抄作业的参考。1. 从“自己背不完”到“想帮别人背”这个小破站是怎么来的1.1 为什么做“八股文小破站”它到底解决什么问题起因特别朴素就是我自己准备面试的时候发现每天收藏一堆“面试必备100题”、“大厂高频考点”但真到复习的时候不知道从哪一题开始也不知道自己到底掌握了多少。收藏夹里躺着几百个链接真正打开的不到十分之一。这种信息过载带来的焦虑感比面试本身还折磨人。市面上其实已经有类似的产品了比如一些在线的题库网站、面试刷题App但我用下来的感觉是要么内容太泛什么岗位都往里塞要么交互太重打开App先弹五个广告再让你登录、签到、解锁会员。我只是想安安静静刷几道题这体验真的不行。所以这个站的核心设计逻辑就三句话内容要纯净分类要清晰操作要轻量。我不需要花里胡哨的社区功能不需要排行榜不需要每日打卡得积分我只需要一个能让我快速找到“进程和线程的区别是什么”、“TCP三次握手为什么是三次”这种问题并且能让我把不会的题目标记下来的地方。这也决定了后面所有的设计决策内容优先性能其次界面极简。1.2 做之前我列的需求清单开始动手之前我没有直接开一个空项目写代码而是先花了一晚上列需求。这个习惯建议所有想做个人站点的朋友都学一下哪怕只是自己用也先写清楚“我要什么”、“我不要什么”。我当时的清单是这样的核心功能面试题浏览、随机刷题、错题记录、搜索内容分类按计算机基础、编程语言、数据库、框架、场景题进行分类题目展示每道题包含问题、答案要点、难度标签、关联知识点数据存储不搞复杂的用户系统先做单机版不需要注册登录部署方式一台轻量云服务器支持HTTPS能公开访问不要什么不做评论区、不做排行榜、不做每日签到、不做付费内容这份清单的重要性在于它帮我在后面几周里拒绝了至少二十个“顺手加个功能”的念头。做个人站最怕的就是需求蔓延今天想加个暗色模式明天想加个题目讨论区后天想加个用户积分系统最后项目永远发布不了。2. 整体架构与技术选型小破站也需要有清晰的骨架2.1 架构思路静态内容为主动态请求压到最低先明确一个原则面试题这种内容属于典型的“读多写极少”场景。用户大多数时候是在浏览题目、搜索题目偶尔会点击“标记为不会”。这意味着我完全不需要为这个站造一个复杂的后端服务更不需要微服务、消息队列、Redis缓存这些大词。我采用的方案是静态页面 轻量接口。所有题目数据以JSON文件的形式存放在服务器上前端页面通过接口读取这些JSON文件并渲染。由于数据量不大目前大概一千道题左右JSON文件也就几MB根本不需要引入数据库系统。这个思路虽然听起来很“原始”但恰恰是个人站点最稳的一条路没有数据库就意味着少了一个最大的故障点没有复杂的服务端逻辑就意味着部署和迁移都极其简单。架构上具体是这样的前端Vue 3 Vite构建后的静态文件由 Nginx 托管数据层按分类拆分的 JSON 文件放在服务器指定目录接口层用 Node.js 写了一个极简的 Express 服务提供题目列表查询、分类查询、搜索等接口整体部署一台 2核4G 的云服务器Ubuntu 22.04Nginx 做反向代理和静态文件服务可能有朋友会问既然数据是JSON文件为什么不直接让前端读文件还要单独写一个接口层原因有两个一是如果直接暴露JSON文件路径那么搜索和筛选功能就需要在前端做全量数据的过滤虽然也能实现但后续如果数据量上来前端的加载和渲染压力会变大二是接口层可以做一些简单的数据统计比如记录被标记为“不会”的题目数量这些数据如果放在前端处理用户一刷新就丢了。2.2 技术栈的取舍逻辑为什么不用那些“时下流行”的方案这里我想多说几句技术选型的思考过程因为这个决策直接影响后面所有的开发体验。第一个考虑过的方案是Next.js全栈。这是目前个人站点领域非常流行的方案服务端渲染、API路由、静态生成全都有一个框架搞定一切。但我最后放弃了原因是这个站的内容更新频率并不高一共就一千道题我不需要服务端渲染来提升SEO也不需要动态生成页面Vue的客户端渲染完全够用。Next.js对我来说反而增加了很多不需要的复杂度构建链路更长部署时还要照顾到Node服务调试起来也多一层。第二个考虑过的方案是用现成的CMS比如Strapi或者直接套一个WordPress。这个方案直接否决因为题目的结构完全是定制化的CMS需要配置各种字段、关系、权限折腾一遍的成本比我自己写一个接口层还要高。而且WordPress的性能和安全性放在公网上本身就是个负担。第三个考虑过的方案是直接做成纯静态站用VitePress类似的工具把Markdown文件编译成网页。这个方案的优点非常明显部署最简单不需要Node服务驻留但我放弃的原因是需要开发搜索、分类筛选、随机刷题这些交互功能纯静态站实现起来要么依赖第三方搜索服务要么在前端把所有数据一次性加载进来都不是最优解。最终选型是Vue 3 Element Plus做界面Express做轻量接口JSON文件做存储。这套组合的最大特点是每一层我都能完全掌控出了问题随便翻一翻代码就能定位不需要猜框架内部干了什么。2.3 数据模型与内容管理的思路面试题的数据结构我设计得比想象中要细致很多。每道题除了基本的问题和答案我还加了这些字段id题目的唯一标识用于收藏和错题标记category所属大类如“计算机网络”、“操作系统”subcategory所属小类如“TCP/IP”、“进程管理”difficulty难度级别分“入门”、“进阶”、“困难”三档tags关联标签用于跨分类检索answer答案正文以Markdown格式存储source题目来源说明是来自某本经典书还是某公司真题回忆relatedQuestions关联题目ID列表用于知识点串联updatedAt最后更新时间方便追踪内容维护这个结构看起来简单但实际上在后续的内容维护和功能扩展中帮了大忙。比如“tags”字段“进程和线程的区别”这道题可以同时打上“操作系统”、“并发”、“面试高频”这三个标签这样用户在搜任何相关关键词的时候都能找到它。内容管理方式上我没有用在线编辑后台而是直接用Markdown文件维护原始内容然后通过一个Node脚本把Markdown批量转换成JSON。这个流程对于个人维护者来说非常高效我可以直接在本地通过任何编辑器修改题目跑一次脚本就更新线上文件。3. 核心功能拆解与实现八股文站的重点不在“炫技”而在“好用”3.1 面试题库的分类体系按知识域、难度、场景三维组织题目如何分类是这个网站体验好坏的关键因为用户进来第一件事就是找题。我采用的是“大类 小类”两级分类体系。大类按照计算机专业的知识域划分包括计算机网络、操作系统、数据结构与算法、数据库、Java基础、Go语言、设计模式、项目场景题这几大类。每个大类下再分小类比如数据库下面又分索引优化、事务与锁、SQL编写、Redis、MySQL这几个小类。这样的设计来源于我自己复习的习惯。当我集中准备数据库的时候我希望一眼就看到数据库相关的所有题目然后按照索引优化、事务这些细分方向逐个攻破。如果分类太粗几百道题堆在一个页面上等于没有分类。除了分类每道题还带难度标记。这个标记不能是“简单/中等/困难”这种主观性太强的词汇我改成了“入门/进阶/困难”并且有一套自己的定义标准入门概念性题目只要背过就能答上来如“什么是进程”进阶需要结合多个知识点作答如“为什么数据库索引要用B树而不是红黑树”困难需要结合项目经验或明显发散思维如“设计一个短链接系统”这个难度体系在错题统计功能中特别有用能直观看出自己薄弱环节主要集中在哪个难度层级。3.2 刷题模式顺序刷、随机刷、错题集满足不同复习阶段的习惯复习面试题分为不同的阶段每个阶段的刷题需求不一样。所以我在功能设计时为用户提供了三种不同的刷题模式。第一种是顺序刷适合第一轮全面过一遍知识点的时候使用。按分类顺序一道题一道题往下看确保每个知识点都覆盖到。这个模式实现起来最简单就是接口返回指定分类下的全部题目列表前端按顺序渲染。第二种是随机刷适合已经大致过完一轮题目、想要查漏补缺的阶段。每次点击“随机一题”时前端会调用接口后端从当前分类中随机返回一道题。这个模式的设计初衷是模拟真实面试的随机感你不知道下一题会抽到什么这样能检验自己是否真的掌握了知识点而不是单纯顺着列表背下去。第三种是错题集这是整个网站最重要的个人数据。每道题目下方有个“我不会”按钮点击后题目ID会被记录到浏览器的localStorage中。错题集页面通过读取这个ID列表调用批量查询接口把所有标记为“不会”的题目标题和分类展示出来。这个功能没有做服务端存储因为我不想引入用户系统localStorage的方案虽然简陋但完全够用并且天然具备隐私性。这里有一个实现细节值得说一下。错题集的ID列表在localStorage里的存储格式是JSON数组但如果直接存一个普通数组当数据量变大以后每次判断“这道题是否在错题集里”都需要遍历整个数组性能会变差。我改成了对象映射的结构键是题目ID值是标记时间戳。这样判断是否存在只需要一次属性访问操作时间复杂度是O(1)后续如果要做“按加入时间倒序排列”的功能也方便。3.3 搜索与标签系统让“搜得到”成为基本盘搜索功能看着简单但想做好其实有很多细节。一开始我用的方案是最土的关键词模糊匹配前端拿到用户输入后遍历所有题目看问题文本里是否包含这个关键词。这个方案在题目数少于一千的时候体验还行但有几个问题。第一个问题是覆盖范围不够。用户搜“MySQL索引失效”我题库里有一道“什么是索引失效常见原因有哪些”虽然问题文本里包含“索引失效”但“MySQL”这个关键词不在题目文本中结果就搜不出来。解决方案是把搜索范围从只搜索问题文本扩大到搜索问题、答案、标签、分类四个字段。这样命中率提高了很多。第二个问题是分词问题。中文搜索不像英文有天然的空格分词用户输入“索引优化怎么搞”我的题目标题是“简述常见的索引优化手段”这两个句子在字符级别上没有一个完整连续的匹配片段传统的模糊匹配就匹配不上。考虑到我这是小站引入Elasticsearch或者中文分词引擎都属于大炮打蚊子我这里的处理方式比较简单粗暴在数据准备阶段给每道题手动补充了一组“搜索关键词”把你的口语化搜索习惯转换成题目相关的专业词汇。虽然维护起来需要一点人力但效果好且可控。标签系统在搜索中起到的是辅助作用。每道题会打上三到八个标签比如“TCP”、“握手”、“可靠传输”、“滑动窗口”。搜索时标签会作为权重最高的匹配字段只要用户输入的关键词命中标签这道题的排序就会靠前。3.4 记忆曲线提醒让八股文真的能记住而不是“看了就忘”这个小功能是我自己最满意的部分。八股文复习最大的痛点不是找不到题而是今天背完明天忘。为了解决这个问题我给网站加了一个“记忆曲线提醒”功能原理就是对标艾宾浩斯遗忘曲线。用最朴素的话讲人在学习一个新知识后的遗忘速度是规律的一开始忘得最快后面逐渐变慢。如果能在即将遗忘的时间点进行复习记忆效果会显著提升。实现上我把这个功能简化成了一个可操作的任务系统。用户在浏览题目时如果觉得自己掌握了这道题可以点击“我记住了”系统会把这个题目的ID记录到localStorage并生成一个复习计划。复习计划包含四个节点1天后复习、3天后复习、7天后复习、14天后复习。当用户打开网站时系统会检查本地存储中是否有任务节点的日期与今天匹配如果有就在首页弹出一个“今日有X道题需要复习”的提示点击就能进入这组题的复习列表。这个功能不需要任何服务端能力全部基于localStorage和日期计算实现。核心逻辑大约三十行代码但给用户带来的价值我认为比很多花了大价钱做的社区签到功能都高。4. 部署上线的实操复盘从代码写完到能公开访问中间踩了无数坑4.1 服务器、域名、HTTPS的完整配置流程代码写完后上线部署又是一个完整的过程。我购买的是一台国内云服务商的轻量应用服务器配置是2核4G带宽5Mbps系统选择Ubuntu 22.04 LTS。选这个配置的原因很简单个人站点访问量不大4G内存跑一个Node服务加Nginx绰绰有余5Mbps的带宽也足够支持普通图片和JSON数据的响应。域名购买后第一件事就是备案。这个过程比较耗时间不同云服务商的流程略有差异但大致都需要提交身份证信息、网站负责人信息、以及一个简单的网站内容说明。整个审批周期一般一到两周。在备案完成前我通过IP直接访问站点来调试避免等待期间无事可做。HTTPS的配置用的是Let‘s Encrypt的免费证书配合certbot工具自动化申请和续期。具体步骤很简单安装certbotapt install certbot python3-certbot-nginx执行证书签发certbot --nginx -d yourdomain.com工具会自动修改Nginx配置并启用HTTPS需要注意的一点是Let’s Encrypt证书的有效期是90天需要在到期前执行续期命令。certbot会自动创建定时任务来续期但建议配置好之后手动跑一次certbot renew --dry-run确认定时任务能正常执行。4.2 Nginx反向代理与静态资源缓存策略Nginx在这个站里承担了两个职责托管前端静态文件以及把API请求反向代理到Node服务。这是我的Nginx配置片段server { listen 443 ssl http2; server_name yourdomain.com; ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem; # 前端静态资源 root /var/www/jiagou/dist; index index.html; # 解决Vue Router的history模式路由刷新404问题 location / { try_files $uri $uri/ /index.html; } # 反向代理到Node接口服务 location /api/ { proxy_pass http://127.0.0.1:3001; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # 静态资源缓存30天 location ~* \.(js|css|png|jpg|svg)$ { expires 30d; add_header Cache-Control public, immutable; } }这里需要特别注意的是try_files $uri $uri/ /index.html;这一行。如果你的Vue项目使用了history模式的路由用户在地址栏直接输入类似/question/123这种路径访问时Nginx会先查找服务器上是否存在对应的文件找不到就会回退到/index.html然后由前端的Vue Router接管路由。没有这一行配置刷新页面就会出现404这是很多新手部署SPA应用时最容易遇到的问题。4.3 数据备份与内容更新不搞自动化也要搞一个能执行的流程内容型站点最怕的事情是辛苦整理的题库因为服务器故障全部丢失。所以上线当天我就配置了数据备份。由于数据核心就是服务器上的JSON文件备份方案就变得异常简单。我写了一个Shell脚本使用rsync命令在每周日凌晨三点把数据目录增量同步到自己的另一台家用NAS上。为什么用增量而不是全量因为全量每天跑一遍太占空间而且耗时增量同步只传输变更过的文件几秒钟就能完成。内容更新的流程我也做了标准化。平时如果发现新题我会在本地用编辑器打开对应的Markdown文件补充题目内容然后运行一个构建脚本node build-data.js rsync -avz --delete ./data/ rootyourserver:/var/www/jiagou/data/build-data.js脚本会扫描所有Markdown文件将它们转换为格式化的JSON数据并生成一个index.json索引文件。这个流程保证了我不需要登录服务器去编辑JSON也不需要安装数据库管理工具只需要一个本地编辑器和一行命令。5. 常见问题排查与避坑速查表5.1 我亲历过的故障按排查优先级排序上线这一个月里小破站确实挂过几次好在每次都能在短时间内恢复。我把踩过的坑和排查方法整理成一个速查表希望其他人少走弯路。问题表现排查方式我的解决办法网站打不开浏览器提示“无法访问”先检查域名解析是否正常再检查服务器是否宕机云服务商的监控报警功能开启服务器宕机会有短信提醒页面能打开但接口请求一直转圈查看Node服务是否还在运行写了一个简单的systemd服务设置自动重启再也不怕进程挂掉了部署后刷新404检查Nginx的try_files配置配置改成try_files $uri $uri/ /index.html;立即解决图片加载慢检查带宽占用看是不是带宽跑满把所有图片压缩到100KB以内同时开启Nginx的gzip压缩API请求返回502查看Nginx错误日志定位是Node服务崩了还是端口不通通常重启Node服务就能解决长期方案是用pm2守护进程这类故障的排查核心逻辑是“分层定位”。先想清楚问题出在哪一层——是DNS层、网络层、Nginx层还是Node应用层。每层都有对应的日志文件不要凭感觉猜测。我自己的排查顺序是先看浏览器开发者工具里的Network面板确认请求是否发出、返回状态码是什么。如果是502说明Nginx能收到请求但后端服务不可用去看Node服务的日志。如果是404多半是路由或静态文件路径配置问题。这个顺序能帮你在五分钟内定位绝大多数问题。5.2 新手最容易踩的坑我帮你提前排掉第一个坑是云服务器安全组配置。阿里云、腾讯云这类云服务商每台服务器都有一层独立于操作系统的防火墙默认通常只开放80和443端口。如果你的Node服务运行在3001端口需要在云控制台的安全组规则里额外添加一条放行规则。我之前因为只修改了服务器内部的ufw规则没有动安全组导致外部始终无法访问3001端口排查了整整一个晚上。第二个坑是前端接口地址的配置。开发环境下前端调用API是走http://localhost:3001/api但部署到线上后就必须改成https://yourdomain.com/api。如果写死了上线后所有接口请求都会打到本地。我的处理方式是使用Vite的环境变量功能在构建时根据环境变量动态生成API地址开发环境、生产环境各用各的配置文件。第三个坑是打包后的前端资源路径问题。Vite默认的base路径是/如果你的站点部署在域名根路径下没有问题。但如果你暂时没有域名是用IP加端口访问的或者放在某个子目录下就必须修改base配置。这个坑直接决定了页面上的JS和CSS能不能加载出来表现为页面白屏、控制台报错。6. 上线的后续规划与个人经验碎碎念小破站上线之后我才深刻体会到“上线不是结束而是开始”这句话的含义。网站放在公网上以后用户反馈、搜索引擎收录、内容持续更新这些事会接踵而来每一件都需要花时间处理。我后续的计划大概有三条线。第一条线是内容扩充目前题库的深度主要集中在后端方向前端和算法相关的题目数量还比较少后续会逐步补齐。第二条线是功能打磨错题集目前只支持本地存储未来会考虑加入一个极简账号体系让数据可以跨设备同步。第三条线是SEO优化目前搜索引擎的收录情况还不理想需要完善页面的meta标签、生成sitemap并提交到搜索引擎站长平台。就我个人而言做这个站最大的收获不在于学了多少新技术而在于重新理解了一个道理做技术项目选择“足够好”的方案往往比追求“最先进”的方案更能让事情落地。那些看上去很酷的技术栈如果不能帮助用户更快地找到一道面试题的答案那就只是自我感动。这个小破站用的每一行代码都极其朴素但它稳定地跑了一个月没有一次因为架构问题需要重启这对我来说就是最大的赞赏。所以如果你也想做一个自己的小站点我的建议很简单尽快开始便宜够用的服务器就行技术栈选你最有把握的第一版能跑起来就上线。然后在这个基础上根据真实的用户反馈一点点迭代。你会发现把一个“能用”的东西慢慢打磨成“好用”的过程比任何技术选型带来的快感都要充实得多。