Mental Model
先建立一个整体心智模型
把这个项目想象成一家线上杂志社:读者在前台看文章,编辑在后台管理文章,服务端像收银台和仓库管理员,数据库像档案室。
浏览器前端
负责把数据变成页面:按钮、卡片、表单、跳转、交互状态都在这里。
服务端 API
负责接收请求并做判断:能不能登录、能不能发评论、要返回哪些文章。
数据库
负责长期保存数据:用户、文章、评论、点赞、收藏都最终存在 PostgreSQL。
共享包
负责减少重复代码:多个应用共用类型和 UI,避免各写各的。
Architecture
系统总览:一个仓库里放了多个应用
这个项目是 Monorepo,意思是多个相关应用和包放在同一个代码仓库中。好处是前台、后台、后端、共享类型可以一起开发、一起校验。
apps/web
博客前台
用户打开的网页,负责首页、搜索、文章详情、登录和用户中心。
apps/admin
管理后台
管理员使用的网页,负责文章、分类、标签、用户和评论管理。
apps/api
服务端 API
NestJS 写成的后端,接收前端请求,执行业务规则,再读写数据库。
apps/android
移动端客户端
Android Kotlin + Compose 客户端,通过同一套 API 获取博客数据。
packages/shared
共享类型
用 Zod 和 TypeScript 描述跨端共用的数据结构,比如文章、用户、评论。
packages/ui
共享 UI
Web 和 Admin 可复用的按钮、标题、标签等视觉组件。
系统协作图
Web / Admin / Android
不同客户端负责收集用户操作,并发起 HTTP 请求。
NestJS API
后端按模块处理业务:文章、用户、评论、收藏等。
Prisma ORM
把 TypeScript 里的查询翻译成数据库能执行的 SQL。
PostgreSQL
保存真实数据,并在下次请求时继续提供。
Frontend
前端页面路由:URL 决定打开哪个页面
Next.js App Router 使用文件夹表示路由。比如 app/search/page.tsx 对应 /search,app/posts/[slug]/page.tsx 对应任意文章详情。
/:首页
请求文章列表和分类列表,展示精选文章、分类 Tab 和文章卡片。
/search:搜索
把用户输入的 q 参数传给后端,在标题、摘要、正文中做模糊检索。
/posts/[slug]:文章详情
根据文章 slug 请求单篇文章,使用 marked 把 Markdown 转成 HTML。
/login:登录
提交邮箱和密码,成功后把 JWT 与用户信息保存到浏览器本地状态。
/profile:用户中心
带着 JWT 请求当前用户资料,并展示收藏等个性化信息。
/tech-docs:技术原理
当前页面,用通俗解释和图示说明整个项目如何工作。
Data Flow
用户访问文章列表时发生了什么
从前端视角看,你调用的是 getPosts;从全栈视角看,它背后是一条从浏览器到数据库再回来的链路。
打开首页
浏览器请求 /,Next.js 执行 app/page.tsx,准备渲染首页。
请求 API
getPosts({ pageSize: 18 }) 拼出 /posts?pageSize=18,并发请求分类。
后端查询
PostsController 调用 PostsService,Prisma 查询 PUBLISHED 状态文章。
渲染卡片
API 返回 JSON,前端把 items 交给 PostCard 组件显示。
请求链路图
Backend
后端 API 分层:Controller 接请求,Service 做业务
NestJS 常见结构是 Module 组织功能,Controller 暴露 HTTP 接口,Service 写业务逻辑,PrismaService 负责连接数据库。
AuthModule
注册、登录、生成 JWT、校验用户身份。
UsersModule
读取当前用户资料,管理用户相关信息。
PostsModule
文章列表、详情、创建、更新、归档、点赞状态。
CategoriesModule
文章分类的查询和管理。
TagsModule
标签的查询和管理。
FavoritesModule
收藏、取消收藏和收藏数量维护。
CommentsModule
评论发布、展示和管理。
AssetsModule
上传图片等静态资源。
Controller
像前台接待员,负责识别请求路径、参数和请求体,例如 GET /posts。
Service
像业务负责人,决定如何查询、创建、更新、校验和序列化数据。
PrismaService
像数据库翻译员,把代码里的对象查询转换成数据库操作。
Authentication
登录鉴权:JWT 是一张可验证的临时通行证
用户输入密码后,后端不会明文保存密码,而是保存 bcrypt 哈希。登录成功后,后端签发 JWT,前端后续请求把它放到 Authorization 头里。
提交账号密码
前端把邮箱和密码提交到 /auth/login 或 /auth/register。
校验密码
后端用 bcrypt.compare 比较用户输入与数据库里的 passwordHash。
签发 JWT
JwtService 把用户 id、email、role 签进 accessToken。
Guard 放行
访问受保护接口时,JwtAuthGuard 验证 token,RolesGuard 判断角色。
密码哈希
数据库保存 passwordHash,而不是原始密码。
Bearer Token
请求头格式是 Authorization: Bearer accessToken。
角色权限
管理员接口要求 ADMIN,普通用户不能直接调用。
Database
核心数据模型:理解接口数据从哪里来
前端拿到的文章卡片不是凭空生成的,它来自数据库中的多张表。Prisma schema 描述了每张表有什么字段,以及表之间怎么关联。
User
关键字段
email、passwordHash、name、role
关系
作者、评论者、点赞者、收藏者
Post
关键字段
slug、title、excerpt、contentMarkdown、status
关系
属于作者,可关联分类、标签、评论、收藏、点赞
Category
关键字段
name、slug、sortOrder
关系
一类文章的集合
Tag
关键字段
name、slug
关系
通过 PostTag 与多篇文章关联
Comment
关键字段
content、status、parentId
关系
属于文章和用户,也可以形成回复树
Favorite / Like
关键字段
userId、postId
关系
记录某个用户是否收藏或点赞某篇文章
简化关系图
Clients
管理后台和 Android:不同入口,共用同一个后端
前台、后台、移动端不是三套完全独立系统。它们可以有不同界面,但核心数据都来自 apps/api,所以后端接口和共享类型尤其重要。
博客前台
主要服务读者:浏览文章、搜索、登录、收藏、点赞、评论。
管理后台
主要服务管理员:创建文章、维护分类标签、审核评论、管理用户。
Android 客户端
主要服务移动端用户:用原生界面消费同一批文章和用户数据。
Deployment
线上发布:让任意地方的用户都能通过网址访问
本地开发时,服务只在你的电脑上运行。要让公网用户访问,需要把前台、后台、后端和数据库部署到线上,并配置域名、HTTPS 和环境变量。
推荐最小上线架构
your-domain.com
博客前台,对应 apps/web,让普通用户浏览文章和技术文档。
admin.your-domain.com
管理后台,对应 apps/admin,让管理员维护文章、分类、标签和评论。
api.your-domain.com/api
后端接口,对应 apps/api,给前台、后台、Android 提供数据。
PostgreSQL
线上数据库,保存用户、文章、评论、收藏、点赞等真实数据。
准备云服务器和域名
购买或准备 Ubuntu 云服务器,开放 80、443、22 端口;把 your-domain.com、admin.your-domain.com、api.your-domain.com 解析到服务器公网 IP。
部署线上 PostgreSQL
创建线上数据库,配置 DATABASE_URL,执行 Prisma generate、migrate,首次部署可按需执行 seed 写入默认管理员和示例数据。
部署 NestJS API
发布 apps/api,配置 DATABASE_URL、JWT_SECRET、PORT,让服务通过 https://api.your-domain.com/api 对外提供接口。
部署 Next.js 前台和后台
发布 apps/web 和 apps/admin,把 NEXT_PUBLIC_API_BASE_URL 指向 https://api.your-domain.com/api。
配置 Nginx 和 HTTPS
用 Nginx 把三个域名反向代理到 web、admin、api 服务端口,再用 Certbot 申请并自动续期 HTTPS 证书。
完成上线验收
逐个检查首页、技术文档、搜索、文章详情、登录、收藏点赞评论、管理后台和 Swagger 是否正常。
Docker Compose 发布思路
生产环境可以新增 docker-compose.prod.yml,并分别给 apps/api、apps/web、apps/admin 准备 Dockerfile。发布时先启动数据库,再执行 Prisma 迁移,最后启动 API、Web 和 Admin。
常用发布命令顺序
pnpm installpnpm db:generatepnpm db:migratepnpm build启动 API、Web、Admin 服务上线安全检查
- JWT_SECRET 必须换成足够长的随机字符串。
- 数据库密码不能使用示例密码,PostgreSQL 不要暴露公网 5432 端口。
- 不要把 .env 提交到 Git 仓库。
- 默认管理员密码上线后立刻修改。
- 正式环境要定期备份数据库。
上线后逐项验收
依次访问 https://your-domain.com、/tech-docs、/search、/posts/某篇文章slug、https://api.your-domain.com/api/posts、https://api.your-domain.com/docs、https://admin.your-domain.com,并检查登录、搜索、文章详情、收藏点赞评论和后台管理是否正常。
Monitoring
线上监控:健康检查与自动修复
部署完成后,服务可能因为内存溢出、数据库断连、配置错误等原因崩溃。health-check.sh 脚本定期检查所有关键进程,发现异常自动修复,修复失败则触发完整重部署。
检测进程
遍历 PM2 进程、PostgreSQL 容器、Nginx 服务,确认每个组件的运行状态。
检查日志
curl 测试 API 和 Web 端点,确认服务能正常响应 HTTP 请求。
尝试重启
对单个失败组件执行 pm2 restart 或 docker restart,等几秒后重新检测。
完整重部署
如果单组件重启仍失败,自动执行 deploy.sh 做一次完整的代码拉取、构建和发布。
检查项与修复策略
PM2 进程
目标:tech-blog-api / web / admin
检测:pm2 jlist 检查 status 是否为 online
修复:pm2 restart <name>
PostgreSQL
目标:Docker 容器 tech-blog-postgres
检测:docker inspect + pg_isready
修复:docker restart tech-blog-postgres
Nginx
目标:反向代理服务
检测:systemctl is-active nginx
修复:systemctl restart nginx
API 可用性
目标:http://127.0.0.1:4000/api/posts
检测:curl 请求是否返回 200
修复:触发 deploy.sh 完整重部署
Web 可用性
目标:http://127.0.0.1:3000
检测:curl 请求是否返回 200
修复:触发 deploy.sh 完整重部署
配置定时检查
在服务器上添加 cron 任务,每 5 分钟自动执行一次健康检查:
crontab -e*/5 * * * * bash /opt/tech-blog/app/scripts/health-check.sh >> /var/log/tech-blog-health.log 2>&1常用运维命令
pm2 status — 查看所有 PM2 进程状态pm2 logs — 查看实时日志docker ps — 查看运行中的容器tail -f /var/log/tech-blog-health.log — 查看健康检查日志bash scripts/health-check.sh --dry-run — 模拟检查,不执行修复Maintenance
后续修改时要同步维护这份文档
技术文档不是一次性页面。只要功能、接口、数据模型或权限流程变了,就应该把这里当作项目说明书一起更新。
同步更新清单
- 新增页面或改路由时,同步更新“前端页面路由”章节。
- 新增 API、修改接口参数或返回值时,同步更新“后端 API 分层”和“请求链路图”。
- 修改 Prisma model、字段或关联关系时,同步更新“核心数据模型”。
- 修改登录、权限、Token 存储逻辑时,同步更新“登录鉴权流程”。
- 新增端侧应用或共享包职责变化时,同步更新“系统总览”。
- 新增监控检查项或修改自动修复逻辑时,同步更新“线上监控”章节。