本章是整个课程的收官项目。在 PostgreSQL 版博客系统的基础上,加入用户认证、权限管理、环境变量规范,并按 MVC 思路拆分代码结构。
codes/ 目录提供完整的参考实现,供自行对照。
首页:最新博客置顶展示 + 分页博客列表
按标签筛选:列表展示该标签下所有博客及作者
博客详情:展示正文、作者、发布时间
登录 / 注册
用户资料页:显示头像、用户名、邮箱、用户组,左侧导航根据权限动态显示
Editor / Admin — 管理我的博客:带删除确认弹窗
Admin — 管理所有博客
Admin — 管理用户列表(含修改密码 / 删除)
博客系统
- 首页展示最新博客 + 分页列表
- 按标签筛选博客
- 博客详情页(展示内容、作者、标签)
- 登录用户可创建/编辑/删除自己的博客
- 管理员可删除任意博客
用户认证
- 本地注册/登录(用户名 + 密码,bcrypt 加密)
- Google OAuth 2.0 登录
- 已登录用户访问登录页自动跳转到个人资料页
权限管理
三个用户组(groups 表):
| group_id | name | 权限 |
|---|---|---|
| 1 | admin | 所有权限,包括用户管理 |
| 2 | editor | 可发布博客,可管理自己的文章 |
| 3 | guest | 只能浏览,不能发布 |
用户管理(仅 admin)
- 查看所有用户列表(分页)
- 修改用户密码
- 删除用户(不能删除自己)
| 类别 | 要求 |
|---|---|
| 数据库 | PostgreSQL,使用 pg 包的 Pool 模式 |
| 认证 | passport + passport-local + passport-google-oauth20 |
| Session | express-session + connect-pg-simple(Session 存入 PostgreSQL) |
| 密码 | bcrypt 加密,禁止明文存储 |
| 环境变量 | dotenv,所有敏感信息放 .env,.env 加入 .gitignore |
| 模板引擎 | EJS(服务端渲染) |
| 前端交互 | 原生 TypeScript + Fetch API,编译后作为静态文件服务 |
groups -- 用户组(admin / editor / guest)
users -- 用户(含 google_id、avatar、provider 字段,支持 OAuth)
blogs -- 博客文章
tags -- 标签
blog_tags -- 博客-标签多对多关联表详见 codes/backend/data/console.sql。
backend/
src/
env.ts ← 加载 .env,必须是 app.ts 的第一个 import
config.ts ← 集中管理 dbConfig 和 sessionConfig
app.ts ← 中间件注册、路由挂载、优雅关闭
db/ ← 数据库访问层(只查表,不做业务判断)
models/ ← TypeScript 接口定义
routes/
api/ ← 返回 JSON 的 API 路由
web/ ← 返回 HTML 的页面路由
utils/
authCheck.ts ← isAuthenticated / isAdmin 中间件
configPassport.ts ← Passport 策略配置(Local + Google)
shutdownConnection.ts ← 优雅关闭数据库连接
views/ ← EJS 模板
public/ ← 静态资源(CSS、图片、编译后的前端 JS)
data/console.sql ← 数据库建表脚本和示例数据
frontend/
src/ ← 前端 TypeScript 源码
tsconfig.json ← 编译目标:backend/public/js/
docker/
docker-compose.yml ← 一键启动 PostgreSQL 容器
项目提供了 Docker Compose 脚本,推荐在 WSL 中运行:
cd docker
docker compose up -d这会启动一个 PostgreSQL 16 容器(端口 5432),数据库名 db6,持久化到 Docker volume pg_data。
停止:
docker compose down停止并删除数据:docker compose down -v
cd codes/backend
cp .env_example .env
# 编辑 .env,填入数据库连接信息和 Google OAuth 凭据.env 示例内容参考 .env_example。
cd codes/backend
npm install
npm run dev启动时会自动完成建表和测试数据的初始化(initializeDatabase()),无需手动执行 SQL。
cd codes/frontend
tsc -w访问 http://localhost:3000
内置测试账号(密码均为
123123):alice(admin)、bob(editor)、charlie(guest)等。
分层原则
db/*.ts只负责查表,不对结果做业务判断(查不到返回undefined,出错抛异常)routes/*.ts负责业务判断(检查 null、权限校验、拼装响应)- 两层各司其职,互不越权
Passport 序列化
登录后 Session 里只存 user.id,每次请求通过 deserializeUser 从数据库重新加载完整用户数据。因此 req.user 始终是最新数据。
Google OAuth 账号合并逻辑
- 先用
google_id查找 → 已用 Google 登录过的老用户 - 再用邮箱查找 → 用邮箱注册过但首次用 Google 登录,自动绑定
- 都没找到 → 创建新用户
完成这个项目,意味着你已经从零开始,亲手搭建了一个完整的 Web 应用——它有自己的数据库、自己的认证体系、自己的前后端交互。每一个按钮背后的逻辑,每一次页面跳转的流转,你都清楚地知道发生了什么。
这种感觉,是跳过这个阶段的人很难拥有的。
如今的前端开发,Vue、React 几乎已经成了行业的默认语言。你可能会发现,身边很多人学 Web 开发,第一步就是打开某个框架的官方文档。他们能很快地搭出一个页面,却往往说不清楚数据是怎么流动的,不明白为什么状态变了视图却没有更新,遇到一个稍微偏门的问题就不知所措。
原因其实很简单——他们从未真正和浏览器"面对面"谈过话。
而你不一样。你用原生的方式操作过 DOM,手写过 Fetch 请求,自己处理过分页逻辑和权限跳转。这些事情做起来繁琐,却正因为繁琐,你才真正理解了框架在替你做什么。
当你日后第一次看到 Vue 的响应式数据,或者 React 的组件渲染,大概率会有一种"原来如此"的豁然开朗——那不是陌生的魔法,而是你已经熟悉的事情,换了一种更优雅的写法。
地基打得扎实,楼才能盖得高。








