基于Next.js与Nest.js的全栈CMS系统Wipi部署与架构解析
1. 项目概述:一个为创作者量身打造的全栈内容管理系统
如果你是一个喜欢折腾技术、同时又希望拥有一个完全自主可控的个人内容发布平台的开发者或博主,那么 Wipi 这个项目很可能就是你一直在寻找的“梦中情站”。它不是另一个 WordPress 或 Hexo,而是一个基于现代 Web 技术栈(Next.js + Nest.js)构建的、从数据库到前端界面都深度集成的开源 CMS 系统。我花了几天时间,从零开始部署、配置并深度体验了 Wipi,它给我的感觉更像是一个为技术型创作者准备的“乐高积木”——基础功能扎实,架构清晰,并且给你留足了自定义和扩展的空间。
简单来说,Wipi 的核心定位是一个面向个人的、全栈的、开箱即用的内容管理解决方案。它不仅仅能发布文章,还内置了“知识小册”(类似 GitBook 的文档集)、独立页面管理、完整的评论系统、访客统计,甚至集成了邮件服务和第三方登录。这意味着你可以用它搭建一个功能完备的个人博客、技术文档站、甚至是小型知识付费的雏形平台。技术栈的选择非常“时髦”且高效:React 生态的 Next.js 负责前端渲染,带来极佳的性能和 SEO 体验;Node.js 领域的“Spring” —— Nest.js 构建健壮的后端 API;MySQL 作为可靠的数据存储;再搭配阿里云 OSS 处理文件上传,构成了一套成熟且高性能的技术组合拳。
接下来,我将以一个实际部署者的视角,带你深入 Wipi 的每一个角落。我会详细拆解它的技术架构、手把手带你完成从本地开发到生产部署的全过程,并分享我在配置和踩坑中积累的一手经验。无论你是想直接使用它,还是想学习如何构建一个类似的全栈应用,这篇文章都能给你提供扎实的参考。
2. 技术架构深度解析:为什么是 Next.js + Nest.js?
在决定采用一个开源项目之前,理解其技术选型背后的逻辑至关重要。这不仅能帮你评估它是否适合你的技术栈,也能让你预见到未来的维护成本和扩展可能性。Wipi 选择 Next.js 和 Nest.js 作为核心框架,绝非偶然,而是经过深思熟虑的架构决策。
2.1 前端:Next.js 带来的渲染范式与性能优势
Wipi 的前端部分基于 Next.js,这是一个建立在 React 之上的全栈框架。它的核心价值在于提供了多种渲染策略,而 Wipi 巧妙地利用了这一点。
服务端渲染(SSR)与静态生成(SSG)的混合应用:对于博客文章详情页、知识小册页面这类内容相对固定、对 SEO 要求极高的页面,Wipi 理论上可以采用 SSG 在构建时生成静态 HTML,实现最快的加载速度和最好的搜索引擎友好度。而对于文章列表、带过滤条件的页面,则可以使用 SSR,在每次请求时生成页面,确保内容的动态性。这种按需选择的灵活性,是传统单页应用(SPA)或纯静态站点生成器(如 Hexo)难以比拟的。在实际使用中,我观察到文章列表页的加载速度非常快,这正是 Next.js 自动优化资源加载和代码分割的功劳。
基于文件系统的路由:Next.js 的pages目录结构直接映射为路由,这让前端路由的管理变得异常直观。例如,pages/posts/[id].tsx就自动处理了/posts/1这样的动态路由。对于 Wipi 这样路由结构相对固定的内容型应用,这种约定大于配置的方式极大地简化了开发。
API Routes 的克制使用:Next.js 也支持在pages/api目录下编写后端 API。但 Wipi 并没有滥用这个功能,而是将核心业务逻辑全部交给了后端的 Nest.js 服务。前端 Next.js 应用主要承担渲染层和少量前端交互 API(如上传进度查询)的职责。这种职责分离是清晰的,避免了 Next.js API Routes 在复杂业务场景下可能导致的代码混乱。
实操心得:Next.js 版本与配置在拉取 Wipi 代码时,务必注意其 package.json 中 Next.js 的版本。不同大版本间(如 12 到 13,13 到 14)可能存在破坏性更新。我建议在熟悉项目后,可以尝试升级到较新的稳定版,以获得更好的性能和新特性(如 App Router),但初期请严格使用项目指定的版本,避免不必要的兼容性问题。另外,
.env文件中CLIENT_ASSET_PREFIX的配置对于 CDN 部署至关重要,如果设置错误,会导致图片、样式等静态资源加载失败。
2.2 后端:Nest.js 提供的企业级结构与可维护性
如果说 Next.js 让前端开发变得愉快,那么 Nest.js 则让 Node.js 后端开发拥有了类似 Java Spring 的严谨和结构。Wipi 的后端完全基于 Nest.js 构建,这带来了几个显著好处:
模块化与依赖注入(DI):Nest.js 强制采用模块化设计。在 Wipi 的源码中,你可以清晰地看到AuthModule、PostModule、CommentModule等。每个模块封装了相关的控制器、服务、实体。这种结构使得代码功能边界清晰,耦合度低,无论是阅读代码还是添加新功能,都能快速定位。依赖注入机制让单元测试变得更容易,你可以轻松地 Mock 掉数据库服务或邮件服务进行测试。
TypeScript 全程护航:Nest.js 与 TypeScript 是绝配。从数据库实体(Entity)、数据传输对象(DTO)到接口(Interface),全程都有严格的类型定义。这在 Wipi 这样的中型项目中是巨大的优势,它能有效减少运行时错误,提升开发体验。例如,当你编写一个新的文章创建接口时,IDE 能自动提示CreatePostDto中有哪些字段,以及它们的类型是什么。
集成的技术生态:Nest.js 拥有丰富的官方和社区模块。Wipi 使用了@nestjs/typeorm来集成 TypeORM(操作 MySQL),使用@nestjs/jwt处理 JWT 认证,使用@nestjs/config管理环境变量。这些经过封装和集成的模块,比直接使用原生库更规范,也减少了样板代码。查看src/app.module.ts文件,你能一目了然地看到整个应用依赖了哪些核心模块。
分层架构的清晰体现:典型的 Nest.js 应用遵循“控制器(Controller) - 服务(Service) - 仓库(Repository)”的分层模式。控制器负责处理 HTTP 请求和响应;服务包含核心业务逻辑;仓库负责与数据库交互。Wipi 的代码严格遵循了这一模式。例如,当你调用POST /api/posts创建文章时,请求会先到达PostController,然后调用PostService中的create方法,该方法内部再通过PostRepository将数据持久化到 MySQL。这种分层使得每一层的职责单一,易于维护和替换。
2.3 数据层:TypeORM 与 MySQL 的协作
Wipi 使用 TypeORM 这个 ORM 框架来操作 MySQL。TypeORM 支持通过装饰器(如@Entity(),@Column())来定义数据模型,这种声明式的方式非常直观。
实体关系映射:在src/entity目录下,你可以找到PostEntity(文章)、UserEntity(用户)、CommentEntity(评论)等所有实体定义。它们之间通过装饰器建立了关系,例如一篇文章对应多个评论(@OneToMany),一个评论属于一篇文章和一个用户(@ManyToOne)。TypeORM 会自动处理这些关联查询,让你能用面向对象的方式操作关系型数据。
数据库迁移:一个成熟的项目需要管理数据库 schema 的变更。TypeORM 提供了迁移(Migration)功能。虽然 Wipi 的文档没有强调,但在生产环境中,当你新增一个字段或表时,应该通过创建迁移文件来执行变更,而不是直接修改数据库。这保证了团队协作和部署流程的可靠性。我建议在项目根目录下研究ormconfig.js或ormconfig.json文件(如果存在),或者查看package.json中的 typeorm 脚本,来了解如何生成和运行迁移。
2.4 文件存储:云服务与本地存储的桥梁
Wipi 默认集成了阿里云 OSS,这是一个非常实用的设计。对于个人项目,将图片、附件等静态资源放在云对象存储上,可以极大地减轻自身服务器的带宽和存储压力,并且能利用 CDN 加速访问。
可扩展的存储抽象:查看FileService相关的代码,你会发现 Wipi 对文件上传操作进行了一定程度的抽象。虽然默认实现是阿里云 OSS,但其接口设计理论上允许你替换为其他云服务(如腾讯云 COS、七牛云)甚至本地存储。如果你只是在本地测试,完全可以修改配置,将文件暂时保存到服务器的某个目录下。
安全考量:文件上传功能永远是安全的重灾区。Wipi 在后端应该(也必须)对上传文件的类型、大小、内容进行校验,防止上传恶意脚本或过大的文件拖垮服务。在部署到公网前,务必确认这些安全检查是否完备。
3. 从零到一:本地开发环境搭建与核心配置详解
理论说得再多,不如亲手跑起来。这一部分,我将带你一步步搭建 Wipi 的本地开发环境,并深入讲解那些容易踩坑的配置项。请确保你的机器上已经安装了 Node.js(建议 LTS 版本,如 18.x)、Docker 和 Git。
3.1 数据库准备:使用 Docker 快速启动 MySQL
Wipi 依赖 MySQL 5.7 或更高版本。使用 Docker 是避免污染本地环境、保证版本一致性的最佳实践。
# 拉取 MySQL 5.7 镜像 docker pull mysql:5.7 # 运行 MySQL 容器 docker run -d \ --name wipi-mysql \ --restart=always \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=your_strong_root_password \ -e MYSQL_DATABASE=wipi \ mysql:5.7 \ --character-set-server=utf8mb4 \ --collation-server=utf8mb4_unicode_ci这里有几个关键参数和操作意图:
--name wipi-mysql:为容器指定一个名字,方便后续管理。--restart=always:确保容器在 Docker 服务重启或容器意外退出时自动重启,这对于生产环境意识很重要。-p 3306:3306:将容器的 3306 端口映射到宿主机的 3306 端口,这样你的本地应用才能连接。-e MYSQL_ROOT_PASSWORD:设置 root 用户的密码。务必替换your_strong_root_password为一个强密码!-e MYSQL_DATABASE=wipi:容器启动时自动创建名为wipi的数据库。- 最后的
--character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci是直接传递给 MySQL 服务的参数,用于设置默认字符集为utf8mb4。这一点至关重要!utf8mb4是utf8的超集,支持存储所有的 Unicode 字符,包括 Emoji 表情。如果你的博客内容或评论里可能出现 Emoji,必须使用此字符集,否则会导致存储错误。
执行完命令后,你可以通过docker ps查看容器是否运行正常。如果需要进入容器内的 MySQL 命令行进行验证,可以执行:
docker exec -it wipi-mysql mysql -u root -p然后输入你设置的密码。
3.2 获取代码与依赖安装
接下来,我们获取 Wipi 的源代码并安装依赖。
# 克隆项目,使用 --depth=1 只克隆最近一次提交,速度更快 git clone --depth=1 https://github.com/fantasticit/wipi.git my-wipi-blog cd my-wipi-blog # 使用 pnpm 安装依赖(速度更快,磁盘空间更省) # 如果没有 pnpm,先安装:npm install -g pnpm pnpm install这里我强烈推荐使用pnpm。相比传统的npm,pnpm通过硬链接的方式共享依赖,安装速度极快,且能节省大量磁盘空间。项目本身也在package.json中推荐使用pnpm。
3.3 环境变量配置:.env 文件的每一个字段
Wipi 使用.env文件管理配置。项目根目录下应该有一个.env.example或直接就是.env文件。我们需要根据本地环境修改它。这是整个配置的核心,也是最容易出错的地方。
# ==================== 客户端配置 ==================== CLIENT_PORT=3001 CLIENT_SITE_URL=http://localhost:3001 CLIENT_ASSET_PREFIX=/ # ==================== 管理后台配置 ==================== ADMIN_PORT=3002 ADMIN_ASSET_PREFIX=/ # ==================== 服务端配置 ==================== SERVER_PORT=3003 SERVER_API_URL=http://localhost:3003/api SERVER_API_PREFIX=/api # ==================== 管理员账户(首次启动自动创建) ==================== ADMIN_USER=admin ADMIN_PASSWD=admin # 强烈建议首次登录后立即修改! # ==================== 数据库配置(与 Docker 容器匹配) ==================== DB_HOST=127.0.0.1 # 如果 MySQL 不在本机,请改为容器 IP 或主机名 DB_PORT=3306 DB_USER=root DB_PASSWD=your_strong_root_password # 与 Docker 启动时设置的密码一致 DB_DATABASE=wipi # ==================== 第三方登录(可选,后续配置) ==================== GITHUB_CLIENT_ID=0 GITHUB_CLIENT_SECRET=0配置项深度解析:
- 端口冲突:
CLIENT_PORT、ADMIN_PORT、SERVER_PORT默认分别为 3001, 3002, 3003。请确保你本地这些端口没有被其他程序(如另一个 Next.js 项目)占用。 - URL 配置的因果关系:这是关键!
CLIENT_SITE_URL:这是前台博客最终被访问的地址。在本地开发时,它就是http://localhost:3001。但如果部署到线上域名https://blog.yourdomain.com,这里就必须改为https://blog.yourdomain.com。这个值会影响前端生成的链接(如文章永久链接)和某些 API 请求的 Referer 检查。SERVER_API_URL:这是前端应用(Next.js)调用后端 API 的完整地址。在本地,前后端分离,所以是http://localhost:3003/api。在线上部署时,如果你通过 Nginx 将 API 请求代理到了后端服务,这里可能需要改为https://blog.yourdomain.com/api或https://api.yourdomain.com。必须确保前端能通过这个 URL 访问到后端。SERVER_API_PREFIX:这是 Nest.js 后端应用自身监听的路径前缀。保持/api即可,意味着所有后端接口都以/api开头。
- 数据库连接:
DB_HOST在 Docker 默认桥接网络下,从宿主机访问容器用127.0.0.1即可。如果遇到连接问题,可以尝试用docker inspect wipi-mysql | grep IPAddress查看容器的实际 IP。 - 资源路径:
CLIENT_ASSET_PREFIX和ADMIN_ASSET_PREFIX用于设置静态资源(JS、CSS、图片)的公共路径。开发时用/。如果生产环境用了 CDN,这里要填 CDN 的域名,例如https://cdn.yourdomain.com。
3.4 启动项目与初次登录
配置好.env文件后,就可以启动项目了。
# 在项目根目录执行 pnpm run dev这个命令会利用concurrently之类的工具,同时启动前端客户端、后台管理端和后端服务端。稍等片刻,编译完成后,你应该能在终端看到三个服务成功启动的日志。
- 前台博客:打开浏览器,访问
http://localhost:3001。你会看到一个初始化的博客页面,可能内容为空或有一些示例数据。 - 后台管理:访问
http://localhost:3002。使用.env中配置的ADMIN_USER和ADMIN_PASSWD(默认 admin/admin)登录。
首次登录后,第一件事就是去后台的“系统设置”里,修改默认的管理员密码!这是最基本的安全操作。
4. 核心功能实操与个性化定制
登录后台管理系统,你会发现 Wipi 的功能模块排列得井井有条。我们来逐一探索并配置,让它真正变成你的专属站点。
4.1 系统设置:站点的门面与基础
“系统设置”是后台的“控制中心”。在这里,你需要配置站点的核心信息。
- 站点信息:包括站点标题、描述、关键词(SEO用)、Logo、Favicon 等。这些信息会显示在博客页眉页脚和搜索引擎摘要中。建议认真填写,特别是标题和描述,它们直接影响 SEO 效果。
- 评论设置:可以开启或关闭评论功能,设置评论是否需要审核。对于个人博客,初期建议开启审核,防止垃圾评论。
- 邮件服务:Wipi 支持通过 SMTP 发送邮件,例如新评论通知、用户注册验证等。你需要配置 SMTP 服务器(如 QQ 邮箱、Gmail 或你的企业邮箱)、端口、用户名和密码。注意:很多邮箱服务商(如QQ邮箱)需要单独开启 SMTP 服务并获取授权码,而不是直接使用登录密码。
- 第三方登录:除了本地注册,Wipi 支持 GitHub OAuth 登录。这能极大提升访客的评论或注册体验。配置方法如下:
- 登录你的 GitHub,进入
Settings->Developer settings->OAuth Apps->New OAuth App。 Application name填你的博客名,Homepage URL填你的博客地址(如http://localhost:3001),Authorization callback URL填http://localhost:3001/api/auth/github/callback(本地开发时)。- 注册成功后,你会得到
Client ID和Client Secret。将它们分别填入.env文件的GITHUB_CLIENT_ID和GITHUB_CLIENT_SECRET,然后重启后端服务。 - 在后台管理系统的“系统设置”中,应该会出现 GitHub 登录的开关,开启它。
- 登录你的 GitHub,进入
4.2 内容创作:文章、页面与小册
这是 Wipi 的核心内容管理功能。
- 文章管理:支持富文本编辑器(默认可能是 Markdown 编辑器)创作。你可以设置文章标题、内容、摘要、封面图、分类、标签、发布时间和状态(发布/草稿)。实操技巧:善用“摘要”字段。如果留空,系统通常会自动截取文章前 N 个字作为列表页摘要,但这可能不理想。手动撰写一段吸引人的摘要,能有效提升列表页的点击率。
- 页面管理:用于创建“关于我”、“友情链接”、“项目”等独立页面。这些页面通常不包含在文章流中,拥有独立的链接(如
/about)。 - 知识小册:这是 Wipi 的特色功能,类似于一个迷你的 GitBook。你可以创建一个小册(如“React 入门指南”),然后为其添加多个章节。小册有独立的封面和描述,适合用来整理系列教程或电子书。使用心得:小册的目录结构是线性的(章节顺序),暂时不支持复杂的嵌套目录。对于知识结构梳理,这是一个轻量而好用的工具。
4.3 运营与互动:评论、统计与用户
- 评论管理:在这里你可以审核、回复、编辑或删除访客的评论。一个活跃的评论区是博客的宝贵财富,及时互动能增加用户粘性。
- 访问统计:Wipi 内置了基础的访问统计看板,可能会展示 PV(页面浏览量)、UV(独立访客)等数据。这对于了解内容受欢迎程度很有帮助。注意:内置统计通常比较简单,对于深度数据分析,可以考虑集成 Google Analytics 或 Umami 等专业工具。
- 用户管理:除了管理员,如果开启了访客注册功能,你可以在这里管理注册用户,分配不同的角色和权限。
4.4 文件管理:图床与附件
所有通过文章编辑器或页面编辑器上传的图片、文件,都会在“文件管理”中列出。你可以在这里进行预览、复制链接或删除操作。如前所述,文件默认上传到阿里云 OSS。在上传前,确保已在.env或系统设置中正确配置了 OSS 的AccessKeyId、AccessKeySecret、Bucket和Region。
5. 生产环境部署指南:让博客稳定运行在云端
本地玩得转,最终还是要部署到公网服务器上。Wipi 提供了基于 PM2 的部署脚本,我们来详细拆解每一步。
5.1 服务器准备与基础环境
假设你有一台云服务器(如腾讯云、阿里云的轻量应用服务器),系统为 Ubuntu 22.04 LTS。
- 连接服务器:使用 SSH 登录你的服务器。
- 更新系统与安装基础工具:
sudo apt update && sudo apt upgrade -y sudo apt install -y git curl wget vim - 安装 Node.js:推荐使用 NodeSource 仓库安装 LTS 版本。
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash - sudo apt install -y nodejs node -v # 验证安装,应输出 v18.x.x npm -v - 安装 PM2 和 pnpm:PM2 是 Node.js 应用的进程管理器,能保证应用崩溃后自动重启。
sudo npm install -g pm2 pnpm @nestjs/cli - 安装并配置 MySQL:生产环境不建议用 Docker 简单运行,而是安装服务器版的 MySQL。
登录 MySQL,为 Wipi 创建数据库和专用用户(比直接用 root 更安全):sudo apt install -y mysql-server sudo mysql_secure_installation # 运行安全安装脚本,设置 root 密码并移除不安全设置sudo mysql -u root -pCREATE DATABASE wipi CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER 'wipi_user'@'localhost' IDENTIFIED BY 'a_strong_password_here'; GRANT ALL PRIVILEGES ON wipi.* TO 'wipi_user'@'localhost'; FLUSH PRIVILEGES; EXIT; - 克隆项目代码:
cd /var/www # 或其他你喜欢的目录 sudo git clone --depth=1 https://github.com/fantasticit/wipi.git sudo chown -R $USER:$USER wipi # 将目录所有权改为当前用户,避免权限问题 cd wipi
5.2 生产环境配置
- 复制环境变量文件:
cp .env .env.prod vim .env.prod # 或使用其他编辑器 - 修改
.env.prod文件:这是生产环境专用配置。# 将 localhost 和端口改为你的实际域名和配置 CLIENT_SITE_URL=https://blog.yourdomain.com CLIENT_ASSET_PREFIX=https://blog.yourdomain.com # 如果用了CDN,则填CDN地址 ADMIN_ASSET_PREFIX=https://blog.yourdomain.com SERVER_API_URL=https://blog.yourdomain.com/api # 假设API通过同一域名代理 # 修改管理员密码为强密码 ADMIN_PASSWD=Your_Very_Strong_Admin_Password_123! # 修改数据库连接信息为生产环境的MySQL DB_HOST=localhost DB_PORT=3306 DB_USER=wipi_user # 使用新建的专用用户 DB_PASSWD=a_strong_password_here # 上面创建的密码 DB_DATABASE=wipi # 填写真实的 GitHub OAuth 信息(如果启用) GITHUB_CLIENT_ID=your_github_client_id GITHUB_CLIENT_SECRET=your_github_client_secret # 填写阿里云 OSS 配置(如果启用) OSS_ACCESS_KEY_ID=your_key_id OSS_ACCESS_KEY_SECRET=your_key_secret OSS_REGION=oss-cn-hangzhou OSS_BUCKET=your-bucket-name - 安装依赖并构建:
pnpm install --production # 生产模式安装,只安装 dependencies pnpm run build # 构建 Next.js 前端应用和 Nest.js 后端应用build命令会生成优化后的前端静态文件和后端 JavaScript 代码。
5.3 使用 PM2 启动与管理应用
Wipi 项目根目录下应该有一个ecosystem.config.js或类似的 PM2 配置文件。如果没有,我们可以根据package.json中的脚本自己创建或直接用命令行启动。
查看package.json的scripts,通常会有start:prod这样的命令。我们可以用 PM2 来运行它。
# 进入项目目录 cd /var/www/wipi # 使用 PM2 启动生产环境应用 # 假设 start:prod 命令是 `node dist/main` (Nest.js) 和 `next start` (Next.js) 的组合 # 更常见的是,项目已经配置了并发启动,我们直接运行那个命令 # 例如,如果 package.json 里有 "pm2": "pm2 start ecosystem.config.js" pnpm run pm2 # 如果没有,我们可以分别启动(假设构建后,前端资源在 .next 目录,后端在 dist 目录) # 首先启动后端服务(在后台运行) pm2 start npm --name "wipi-server" -- run start:server-prod # 然后启动前台客户端(Next.js 生产模式) pm2 start npm --name "wipi-client" -- run start:client-prod # 启动后台管理端(如果也是独立的 Next.js 应用) pm2 start npm --name "wipi-admin" -- run start:admin-prod # 设置 PM2 开机自启 pm2 startup # 执行上面命令输出的提示命令(例如 sudo env PATH=$PATH:/usr/bin /usr/lib/node_modules/pm2/bin/pm2 startup systemd -u your_username --hp /home/your_username) pm2 savePM2 常用管理命令:
pm2 list:查看所有运行中的应用状态。pm2 logs wipi-server:查看某个应用的实时日志。pm2 restart wipi-server:重启某个应用。pm2 stop wipi-server:停止某个应用。pm2 delete wipi-server:从 PM2 列表中移除某个应用。
5.4 Nginx 配置与 HTTPS 启用
现在应用已经在3001,3002,3003端口运行了,我们需要用 Nginx 作为反向代理,绑定域名并启用 HTTPS。
- 安装 Nginx:
sudo apt install -y nginx - 申请 SSL 证书:可以使用 Let‘s Encrypt 的免费证书。推荐使用
certbot工具自动化申请和续签。
Certbot 会自动修改 Nginx 配置并启用 HTTPS。sudo apt install -y certbot python3-certbot-nginx sudo certbot --nginx -d blog.yourdomain.com -d admin.blog.yourdomain.com # 如果有多个子域名 - 配置 Nginx 反向代理:如果 certbot 没有自动配置好代理,或者你想自定义,可以编辑 Nginx 站点配置文件(通常在
/etc/nginx/sites-available/下)。
写入以下配置(关键部分已加注释):sudo vim /etc/nginx/sites-available/blog.yourdomain.com
关键配置解析:# 前台博客应用 upstream upstream wipi_client { server 127.0.0.1:3001; keepalive 64; } # 后台管理应用 upstream (如果管理端独立运行在3002端口) upstream wipi_admin { server 127.0.0.1:3002; keepalive 64; } # 后端API upstream upstream wipi_api { server 127.0.0.1:3003; keepalive 64; } server { listen 80; server_name blog.yourdomain.com admin.blog.yourdomain.com; # 将所有 HTTP 请求重定向到 HTTPS return 301 https://$server_name$request_uri; } server { listen 443 ssl http2; server_name blog.yourdomain.com; ssl_certificate /etc/letsencrypt/live/blog.yourdomain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/blog.yourdomain.com/privkey.pem; # 静态资源缓存 location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg)$ { expires 1y; add_header Cache-Control "public, immutable"; # 如果用了CDN,这里可能指向CDN,否则代理到前端服务 proxy_pass http://wipi_client; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } # 前台博客主站 location / { proxy_pass http://wipi_client; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; # 传递真实IP给后端,用于统计或风控 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_cache_bypass $http_upgrade; } # 后端API代理 (假设前端通过 /api 访问后端) location /api/ { proxy_pass http://wipi_api; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; # 这个非常重要! proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_cache_bypass $http_upgrade; } } # 可选:独立的管理后台子域名配置 server { listen 443 ssl http2; server_name admin.blog.yourdomain.com; ssl_certificate /etc/letsencrypt/live/blog.yourdomain.com/fullchain.pem; # 可以使用通配符证书或相同证书 ssl_certificate_key /etc/letsencrypt/live/blog.yourdomain.com/privkey.pem; location / { proxy_pass http://wipi_admin; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_cache_bypass $http_upgrade; } }proxy_set_header X-Real-IP $remote_addr;:这行配置至关重要。它将用户的真实 IP 地址传递给后端服务。如果没有这个,后端Nest.js应用获取到的 IP 将是 Nginx 服务器的 IP(通常是127.0.0.1),这将导致访问统计、反爬虫、日志记录等功能完全失效。Wipi 的原始文档也特别强调了这一点。location /api/:我们将所有以/api开头的请求代理到后端 Nest.js 服务(端口 3003)。这要求前端.env.prod中的SERVER_API_URL必须设置为https://blog.yourdomain.com/api。- 静态资源缓存:对图片、CSS、JS 文件设置长期缓存,可以显著提升站点加载速度。
- 测试并重载 Nginx:
sudo nginx -t # 测试配置文件语法 sudo systemctl reload nginx # 重载配置使其生效
现在,访问https://blog.yourdomain.com应该就能看到你的 Wipi 博客了,而https://blog.yourdomain.com/api的请求会被正确转发到后端。
6. 常见问题排查与进阶优化
即使按照步骤操作,也难免会遇到问题。这里记录一些我遇到过的典型问题及其解决方法。
6.1 数据库连接失败
问题现象:后端服务启动失败,日志报错ECONNREFUSED或Access denied for user。
排查步骤:
- 检查 MySQL 服务状态:
sudo systemctl status mysql。 - 验证数据库连接信息:确保
.env.prod中的DB_HOST,DB_PORT,DB_USER,DB_PASSWD,DB_DATABASE完全正确。特别是密码中的特殊字符是否需要转义。 - 测试远程连接:在服务器上运行
mysql -u wipi_user -p -h 127.0.0.1 wipi,看能否登录。如果不能,检查 MySQL 用户权限和绑定地址。确保 MySQL 的bind-address在/etc/mysql/mysql.conf.d/mysqld.cnf中不是127.0.0.1(如果应用与数据库同机,可以是),或者用户被允许从localhost连接。 - 检查防火墙:确保服务器防火墙(如
ufw)没有屏蔽 3306 端口(本地连接不需要)。
6.2 前端访问后端 API 404 或跨域(CORS)错误
问题现象:博客页面能打开,但文章列表加载不出来,浏览器控制台报错404或CORS policy。
排查步骤:
- 确认后端服务是否运行:
pm2 list查看wipi-server状态,pm2 logs wipi-server查看有无错误。 - 检查 Nginx 配置:确保
location /api/的proxy_pass指向正确的后端地址和端口(http://wipi_api对应 upstream 定义)。 - 检查前端配置:确认
.env.prod中的SERVER_API_URL是https://blog.yourdomain.com/api(与 Nginx 配置的路径一致),而不是http://localhost:3003。 - 检查 Nest.js CORS 配置:在 Nest.js 的
main.ts或AppModule中,应该已经启用了 CORS。如果部署后发现跨域问题,可以检查后端服务是否配置了允许前端域名的请求。生产环境下,最好在 Nginx 层统一处理代理,避免跨域。
6.3 静态资源(图片、CSS、JS)加载失败
问题现象:页面布局错乱,图片不显示。
排查步骤:
- 检查资源路径:打开浏览器开发者工具(F12)的“网络(Network)”选项卡,查看加载失败的资源 URL 是什么。它应该是
CLIENT_ASSET_PREFIX或ADMIN_ASSET_PREFIX配置的路径。 - 检查 Nginx 静态资源代理:确保 Nginx 配置中,对静态资源的
location块正确代理到了前端服务,并且缓存配置没有导致问题。可以尝试先注释掉缓存指令expires和add_header进行测试。 - 检查构建产物:确认
pnpm run build成功执行,并且在项目目录下生成了.next等构建输出文件夹。
6.4 性能与安全进阶优化
- 数据库优化:
- 为文章表(
posts)的slug(别名)、categoryId、status等常用查询字段添加索引。 - 定期清理
logs等日志表,避免数据膨胀。
- 为文章表(
- Node.js 进程优化:
- PM2 可以启动集群模式,利用多核 CPU:
pm2 start ecosystem.config.js -i max。但需要确保你的应用是无状态的,或者妥善处理了 Session/状态共享问题。Wipi 使用 JWT,通常是无状态的,适合集群。 - 调整 PM2 的内存和重启策略。
- PM2 可以启动集群模式,利用多核 CPU:
- 静态资源 CDN:
- 将阿里云 OSS 配置为公开读,并绑定自定义域名(如
cdn.yourdomain.com)。 - 在 OSS 控制台开启 CDN 加速。
- 将
.env.prod中的CLIENT_ASSET_PREFIX和ADMIN_ASSET_PREFIX改为 CDN 域名。 - 这样,图片、JS、CSS 等都将通过 CDN 分发,极大提升全球访问速度。
- 将阿里云 OSS 配置为公开读,并绑定自定义域名(如
- 安全加固:
- 修改默认密码:再次强调,第一时间修改默认管理员密码。
- 保持更新:定期关注 Wipi 项目的 GitHub 仓库,及时更新版本,修复安全漏洞。
- 数据库备份:设置定时任务(cron job),定期使用
mysqldump备份wipi数据库。 - 限制后台访问:可以通过 Nginx 配置,只允许特定 IP 段访问
/admin路径或管理后台子域名,增加一道防线。 - 配置正确的文件权限:确保项目目录的权限合理,例如配置文件
.env.prod不应被 web 用户直接读取。
经过以上步骤,你应该已经拥有了一个运行在自有服务器上、功能完整、性能可观且具备一定安全性的个人内容平台。Wipi 作为一个开源项目,其清晰的结构和现代的技术栈,不仅让你能快速拥有一个博客,更是一个绝佳的全栈学习范本。你可以基于它进行二次开发,添加自己想要的功能,比如接入更详细的统计分析、实现文章加密、或者开发一个主题系统。
