第 07 课
数据库
SQL 基础、ORM、认证与 CRUD 应用。
课程内容
数据库
内存里的数据重启就没了——真实应用需要数据库持久化。本课学习 SQL 基础、在 Node.js 中操作数据库,以及带认证的完整 CRUD。
学习目标
- 会写常用的 SQL(增删改查、条件、关联)
- 理解关系型数据库的表设计与外键
- 在 Node.js 中用 ORM/驱动操作数据库
- 实现注册登录(密码哈希 + JWT)
SQL 速览
-- 建表
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE todos (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
title VARCHAR(200) NOT NULL,
done BOOLEAN DEFAULT FALSE,
FOREIGN KEY (user_id) REFERENCES users(id) -- 外键关联
);
-- 增
INSERT INTO todos (user_id, title) VALUES (1, '学 SQL');
-- 查(带条件和关联)
SELECT t.title, u.username
FROM todos t JOIN users u ON t.user_id = u.id
WHERE t.done = FALSE
ORDER BY t.id DESC
LIMIT 10;
-- 改 / 删
UPDATE todos SET done = TRUE WHERE id = 1;
DELETE FROM todos WHERE id = 1;
Node.js 连接数据库
以 MySQL 为例(用连接池,不要每次请求新建连接):
const mysql = require("mysql2/promise");
const pool = mysql.createPool({
host: "localhost",
user: "root",
password: process.env.DB_PASSWORD, // 密码放环境变量!
database: "todo_app",
});
// 查询(永远用参数占位符 ?,防 SQL 注入)
app.get("/api/todos", async (req, res) => {
const [rows] = await pool.query(
"SELECT * FROM todos WHERE user_id = ?",
[req.userId]
);
res.json(rows);
});
安全红线:绝不把用户输入直接拼进 SQL 字符串,必须用参数化查询。
认证:注册与登录
const bcrypt = require("bcrypt");
const jwt = require("jsonwebtoken");
// 注册:密码哈希后存储(绝不存明文!)
app.post("/api/register", async (req, res) => {
const { username, password } = req.body;
const hash = await bcrypt.hash(password, 10);
await pool.query(
"INSERT INTO users (username, password_hash) VALUES (?, ?)",
[username, hash]
);
res.status(201).json({ message: "注册成功" });
});
// 登录:校验密码,签发 JWT
app.post("/api/login", async (req, res) => {
const { username, password } = req.body;
const [rows] = await pool.query(
"SELECT * FROM users WHERE username = ?", [username]
);
const user = rows[0];
if (!user || !(await bcrypt.compare(password, user.password_hash))) {
return res.status(401).json({ error: "用户名或密码错误" });
}
const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET, {
expiresIn: "7d",
});
res.json({ token });
});
// 认证中间件:保护需要登录的接口
function auth(req, res, next) {
try {
const token = req.headers.authorization;
req.userId = jwt.verify(token, process.env.JWT_SECRET).userId;
next();
} catch {
res.status(401).json({ error: "请先登录" });
}
}
app.get("/api/todos", auth, async (req, res) => { /* ... */ });