同事说我是臭页面仔,我彻底疯狂

729 阅读3分钟

我是彭于晏爱编程,我喂自己袋盐
记录一次前端转全栈的踩坑之旅,方便自己查阅,也期望对同样想 "速成" 的朋友有所帮助。

Node.js 全栈示例:从 Express 脚手架到 JWT 鉴权


1. 背景与动机

页面仔也想成为全栈,想快速了解后端架构而不想学习其他语言、降低成本,所以选择了 Node.js + Express。Express 官网:www.expressjs.com.cn/


2. 使用 Express Generator 初始化项目

# 全局安装(可选)
# npm install -g express-generator

# 1. 使用 npx 直接执行
npx express-generator --view=pug myapp

# 2. 进入目录 & 安装依赖
cd myapp
npm install

# 3. 启动开发服务器
npm start

目录结构

.
├── app.js
├── bin
│   └── www
├── package.json
├── public
│   ├── images
│   ├── javascripts
│   └── stylesheets
│       └── style.css
├── routes
│   ├── index.js
│   └── users.js
└── views
    ├── error.pug
    ├── index.pug
    └── layout.pug

7 directories, 9 files

Tips:若目录截图或结构图在原稿中以图片形式展示,请保留原图片,无需替换。


3. 数据库选型

Express 可配合多种数据库(MySQL、MongoDB、DynamoDB…)。这里选用 MySQL,辅以官方可视化工具 MySQL Workbench

image.png

建库 & 建表(示例)

-- 创建数据库
CREATE DATABASE IF NOT EXISTS express_demo
  CHARACTER SET utf8mb4
  COLLATE utf8mb4_general_ci;

-- 选择数据库
USE express_demo;

-- 创建示例表 project_objects
CREATE TABLE project_objects (
  id          INT          NOT NULL AUTO_INCREMENT PRIMARY KEY,
  name        VARCHAR(50) NOT NULL,
  description TEXT,
  created_at  TIMESTAMP    DEFAULT CURRENT_TIMESTAMP
);

更多增删改查(CRUD)语句本文不赘述,可参见“附录:常用 SQL 备忘”。


4. 连接数据库

在项目根目录下创建 db/ 文件夹,并新增 mysql.js

// db/mysql.js
import dbConfig from "../config/default.js";
import mysql from "mysql2";

const connection = mysql.createConnection({
  host: dbConfig.db.host,
  user: dbConfig.db.user,
  password: dbConfig.db.password,
  database: dbConfig.db.database,
  port: dbConfig.db.port,
  connectTimeout: dbConfig.db.connectTimeout, // 10s
});

console.log("完整的 dbConfig:", dbConfig);

connection.connect(err => {
  if (err) {
    console.error("数据库连接失败:", err);
  } else {
    console.log("数据库连接成功");
  }
});

export default connection;
config/default.js
export default {
  port: 8088,
  db: {
    host: "localhost",
    user: "express_user",
    password: "smq111222",
    database: "express_demo",
    port: 3306,
    connectTimeout: 50000,
  },
  // 生成 JWT 的私钥
  PRIVATE_KEY: "express-demo",
};

5. 编写接口示例

about 相关接口为例,新建 routes/about.js

image.png

import express from "express";
import db from "../../db/mysql.js";

const router = express.Router();

// POST /about/get-detail
router.post("/get-detail", async (req, res) => {
  try {
    // 1. 查询数据库
    const [dbData] = await db.promise().query("SELECT * FROM project_objects");

    res.json({
      code: 200,
      message: "获取成功",
      data: dbData,
    });
  } catch (err) {
    // 失败回退到 mock
    res.json({
      code: 200,
      message: "数据库查询失败,返回备用数据",
      data: null,
    });
  }
});

export default router;

亮点:同样的 JS 语法,前端同学阅读无压力。


6. 登录 & JWT 鉴权

6.1 登录接口(返回 token + userId + role

登录成功后前端需把 Bearer <token> 写入 Authorization 头,并在请求中附带 userId、防重放签名 sig、一次性随机串 bounce 等安全字段(可按需拓展)。

6.2 鉴权中间件 middleware/auth.js

import jwt from "jsonwebtoken";
import DefaultConfig from "../config/default.js";

function auth(req, res, next) {
  // 1. 放行登录
  if (req.path === "/newUser/login" && req.method === "POST") {
    return next();
  }

  // 2. 读取 Authorization
  const authHeader = req.headers.authorization;
  if (!authHeader?.startsWith("Bearer ")) {
    return res.status(400).json({
      code: 4001,
      message: "缺少有效的 Authorization 头(格式应为 Bearer <token>)",
    });
  }
  const token = authHeader.split(" ")[1];

  // 3. 校验 userId
  const userId = req.body.userId || req.query.userId;
  if (!userId) {
    return res.status(400).json({
      code: 4002,
      message: "缺少请求参数 userId",
    });
  }

  // 4. 验证 token
  jwt.verify(token, DefaultConfig.PRIVATE_KEY, (err, decoded) => {
    if (err) {
      return res.status(401).json({
        code: 4003,
        message: "无效的 token",
      });
    }
    if (decoded.id !== Number(userId)) {
      return res.status(403).json({
        code: 4004,
        message: "token 与用户 ID 不匹配",
      });
    }
    req.user = decoded; // 挂载用户信息
    next();
  });
}

export { auth };

image.png

image.png


7. 应用入口 app.js

import express from "express";
import Route from "./routes/index.js";
import defaultConfig from "./config/default.js";
import bodyParser from "body-parser";
import cors from "cors";
import middleware from "./middleware/index.js";
import { auth } from "./middleware/auth.js";

const app = express();

// 记录耗时 & 日志
app.use(middleware.timer);
app.use(middleware.logger);

// 跨域 & 解析 body
app.use(cors());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

// 静态资源
app.use(express.static("public"));
// 访问示例:http://localhost:3000/public/images/bg.jpg

// 鉴权中间件(登录接口已在 auth 内部放行)
app.use(auth);

// 总路由挂载
app.use("/", Route);

app.listen(defaultConfig.port, () => {
  console.log(`服务器启动成功,端口:${defaultConfig.port}`);
});

8. 至此,你已不再是“页面仔”!

  • 数据库建库、建表、CRUD —— 拿捏
  • 接口编写、JWT 鉴权 —— 拿捏
  • 静态资源、日志、跨域、中间件 —— 拿捏

全栈雏形已经跑起来 🎉🎉🎉

PS:本文只是个人学习笔记,若有疏漏欢迎指正。


附录:常用 SQL 备忘(可选)

-- 新增
INSERT INTO project_objects (name, description) VALUES ('Demo', '示例');

-- 查询
SELECT * FROM project_objects;

-- 更新
UPDATE project_objects SET description = '更新' WHERE id = 1;

-- 删除
DELETE FROM project_objects WHERE id = 1;