第6章 后端开发基础
作者
谭策 — 独立开发者 | AIOps 领域探索者
- 🌐 项目官网:ITOpsAgentinfo
- 📝 博客:zjzwfw.cloud
- 📧 邮箱:huawei_network@foxmail.com
- 💬 微信公众号:IT Online

许可证
MPL-2.0 © 谭策
本章导读
前面我们理解了项目的架构,现在进入实战阶段。本章将从零开始编写后端代码,带你完整走一遍:创建路由、编写中间件、开发服务、操作数据库、处理错误。
本章定位:这是你第一次真正动手写后端代码。我们会按照项目的实际编码风格来写,让你写出的代码能直接融入项目。
学习目标
阅读完本章后,你将能够:
- 独立创建新的路由模块并注册到应用
- 编写符合项目规范的中间件
- 开发服务层的业务逻辑
- 使用 better-sqlite3 进行增删改查操作
- 实现 JWT 认证和权限控制
- 编写统一的错误处理
- 使用项目日志工具记录操作
6.1 开发环境准备
6.1.1 启动开发服务器
# 进入后端目录
cd backend
# 安装依赖(首次)
npm install
# 启动开发模式(带热重载)
npm run dev看到以下输出表示启动成功:
🚀 ITOps Agent Platform Backend running on 0.0.0.0:3000
📡 WebSocket server ready
🌍 Environment: development6.1.2 验证接口可用
# 健康检查(不需要认证)
curl http://localhost:3001/health/live
# 返回:
# {"status":"alive","timestamp":"2026-05-27T10:00:00.000Z"}6.2 第一个路由模块
6.2.1 路由文件结构
在项目中创建一个新路由模块非常简单。以创建一个 notes(备忘录) 功能为例:
步骤 1:创建路由文件
touch backend/src/routes/noteRoutes.ts步骤 2:编写路由代码
import { Router, Request, Response } from 'express';
import { db } from '../models/database';
import { randomUUID } from 'crypto';
const router = Router();
// GET /api/notes - 获取所有备忘录
router.get('/', (_req: Request, res: Response) => {
const notes = db.prepare('SELECT * FROM notes ORDER BY created_at DESC').all();
res.json({
success: true,
data: notes
});
});
// GET /api/notes/:id - 获取单个备忘录
router.get('/:id', (req: Request, res: Response) => {
const { id } = req.params;
const note = db.prepare('SELECT * FROM notes WHERE id = ?').get(id);
if (!note) {
return res.status(404).json({
success: false,
message: '备忘录不存在'
});
}
res.json({
success: true,
data: note
});
});
// POST /api/notes - 创建备忘录
router.post('/', (req: Request, res: Response) => {
const { title, content } = req.body;
if (!title) {
return res.status(400).json({
success: false,
message: '标题不能为空'
});
}
const id = randomUUID();
const result = db.prepare(
'INSERT INTO notes (id, title, content, created_at, updated_at) VALUES (?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)'
).run(id, title, content || '');
res.status(201).json({
success: true,
data: {
id,
title,
content: content || ''
}
});
});
// PUT /api/notes/:id - 更新备忘录
router.put('/:id', (req: Request, res: Response) => {
const { id } = req.params;
const { title, content } = req.body;
const existing = db.prepare('SELECT * FROM notes WHERE id = ?').get(id);
if (!existing) {
return res.status(404).json({
success: false,
message: '备忘录不存在'
});
}
db.prepare(
'UPDATE notes SET title = ?, content = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?'
).run(title || existing.title, content !== undefined ? content : existing.content, id);
const updated = db.prepare('SELECT * FROM notes WHERE id = ?').get(id);
res.json({
success: true,
data: updated
});
});
// DELETE /api/notes/:id - 删除备忘录
router.delete('/:id', (req: Request, res: Response) => {
const { id } = req.params;
const existing = db.prepare('SELECT * FROM notes WHERE id = ?').get(id);
if (!existing) {
return res.status(404).json({
success: false,
message: '备忘录不存在'
});
}
db.prepare('DELETE FROM notes WHERE id = ?').run(id);
res.json({
success: true,
message: '备忘录已删除'
});
});
export default router;步骤 3:注册路由到应用
在 app.ts 中添加一行:
import noteRoutes from './routes/noteRoutes';
// 在受保护路由区域添加
app.use('/api/notes', rateLimiter, noteRoutes);步骤 4:创建数据表
在 database.ts 中添加表创建语句:
db.exec(`
CREATE TABLE IF NOT EXISTS notes (
id TEXT PRIMARY KEY,
title TEXT NOT NULL,
content TEXT DEFAULT '',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`);6.2.2 测试路由
# 创建备忘录
curl -X POST http://localhost:3001/api/notes \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{"title":"测试备忘录","content":"这是一条测试内容"}'
# 获取所有备忘录
curl http://localhost:3001/api/notes \
-H "Authorization: Bearer YOUR_TOKEN"
# 获取单个备忘录
curl http://localhost:3001/api/notes/你的备忘录ID \
-H "Authorization: Bearer YOUR_TOKEN"
# 更新备忘录
curl -X PUT http://localhost:3001/api/notes/你的备忘录ID \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{"title":"更新后的标题"}'
# 删除备忘录
curl -X DELETE http://localhost:3001/api/notes/你的备忘录ID \
-H "Authorization: Bearer YOUR_TOKEN"6.3 中间件开发
6.3.1 中间件的标准写法
中间件是 Express 中最重要的机制之一。项目中的中间件遵循统一模式:
import { Request, Response, NextFunction } from 'express';
// 标准中间件签名
export function myMiddleware(req: Request, res: Response, next: NextFunction) {
// 1. 执行逻辑
// 2. 可以选择返回响应(终止请求链)
// 3. 或调用 next() 继续下一个中间件/路由
next();
}6.3.2 项目中的认证中间件详解
让我们逐行分析项目的 JWT 认证中间件:
// backend/src/middleware/auth.ts
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
import db from '../models/database';
import { env } from '../utils/env';
import { tokenBlacklist } from '../services/tokenBlacklist';
// 定义认证后的用户类型
interface AuthUser {
id: string;
username: string;
email: string | null;
role: string;
enabled: number;
}
// 用户缓存(减少数据库查询)
const userCache = new Map<string, { user: AuthUser; expiresAt: number }>();
const USER_CACHE_TTL = 60 * 1000; // 缓存 60 秒
export function authenticateToken(
req: Request & { user?: AuthUser },
res: Response,
next: NextFunction
) {
// 第1步:从请求头提取 Token
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({
success: false,
message: '未提供认证token'
});
}
const token = authHeader.substring(7);
// 第2步:检查 Token 是否在黑名单中
if (tokenBlacklist.isBlacklisted(token)) {
return res.status(401).json({
success: false,
message: 'Token已失效'
});
}
// 第3步:验证 JWT Token
try {
const decoded = jwt.verify(token, env.JWT_SECRET, {
algorithms: ['HS256']
}) as jwt.JwtPayload & { id: string };
// 第4步:从缓存或数据库获取用户信息
let user: AuthUser | null = getCachedUser(decoded.id);
if (!user) {
const dbUser = db.prepare(
'SELECT id, username, email, role, enabled FROM users WHERE id = ?'
).get(decoded.id) as AuthUser | undefined;
if (dbUser) {
user = dbUser;
setCachedUser(decoded.id, user);
}
}
// 第5步:检查用户是否存在且启用
if (!user) {
return res.status(401).json({ success: false, message: '用户不存在' });
}
if (!user.enabled) {
return res.status(403).json({ success: false, message: '账户已被禁用' });
}
// 第6步:将用户信息挂载到请求对象,继续执行
req.user = user;
next();
} catch (error) {
if (error instanceof jwt.TokenExpiredError) {
return res.status(401).json({ success: false, message: 'Token已过期' });
}
return res.status(401).json({ success: false, message: '无效的token' });
}
}认证中间件的执行流程:
请求: GET /api/servers
Authorization: Bearer eyJhbGci...
↓
1. 检查 Authorization 头
↓ 有 → 提取 Token
↓ 无 → 返回 401
↓
2. 检查 Token 黑名单
↓ 不在黑名单 → 继续
↓ 在黑名单 → 返回 401
↓
3. 验证 JWT 签名
↓ 有效 → 获取用户 ID
↓ 无效 → 返回 401
↓
4. 查询用户信息(缓存或数据库)
↓ 找到用户 → 继续
↓ 未找到 → 返回 401
↓
5. 检查用户状态
↓ enabled=1 → 继续
↓ enabled=0 → 返回 403
↓
6. req.user = 用户信息
↓
next() → 路由处理函数6.3.3 角色权限中间件
项目提供了基于角色的权限控制:
export function requireRole(...allowedRoles: string[]) {
return (req: Request & { user?: AuthUser }, res: Response, next: NextFunction) => {
if (!req.user) {
return res.status(401).json({
success: false,
message: '未认证'
});
}
if (!allowedRoles.includes(req.user.role)) {
return res.status(403).json({
success: false,
message: '权限不足'
});
}
next();
};
}使用方式:
// 只有管理员可以删除用户
app.delete('/api/users/:id',
authenticateToken,
requireRole('admin'),
userDeleteHandler
);
// 管理员和普通用户都可以查看
app.get('/api/users',
authenticateToken,
requireRole('admin', 'user'),
userListHandler
);6.3.4 请求追踪中间件
项目为每个请求生成唯一的追踪 ID,方便日志追踪:
// backend/src/middleware/trace.ts
import { Request, Response, NextFunction } from 'express';
import { randomUUID } from 'crypto';
export function traceMiddleware(req: Request, res: Response, next: NextFunction) {
// 为每个请求生成唯一 ID
const traceId = randomUUID();
(req as Request & { traceId: string }).traceId = traceId;
// 在响应头中返回,方便前端调试
res.setHeader('X-Trace-Id', traceId);
next();
}为什么需要请求追踪?
用户报告:"系统报错了!"
没有追踪 ID:
日志: "错误: 数据库连接失败"
日志: "错误: 查询超时"
日志: "错误: 用户不存在"
你: 不知道这些错误是不是同一个请求产生的
有追踪 ID:
日志: "[abc-123] 请求开始: GET /api/servers"
日志: "[abc-123] 数据库查询: SELECT * FROM servers"
日志: "[abc-123] 错误: 查询超时"
你: 精确知道是 /api/servers 请求的数据库查询超时了6.4 服务层开发
6.4.1 服务的设计原则
服务层是业务逻辑的核心。项目中的服务遵循以下设计原则:
- 单一职责:一个服务只负责一个业务域
- 无状态:服务方法不持有请求级别的状态
- 可测试:每个方法都可以独立测试
- 依赖注入:通过参数或初始化注入依赖
6.4.2 登录服务实战
让我们通过实际的登录路由来理解后端开发模式:
// backend/src/routes/authRoutes.ts
import { Router, Request, Response } from 'express';
import { db } from '../models/database';
import bcrypt from 'bcryptjs';
import jwt, { SignOptions } from 'jsonwebtoken';
import { randomUUID } from 'crypto';
import { env } from '../utils/env';
import { logger } from '../utils/logger';
const router = Router();
// POST /api/auth/login
router.post('/login', async (req: Request, res: Response) => {
try {
const { username, password } = req.body;
// 1. 查询用户
const user = db.prepare(
'SELECT * FROM users WHERE username = ?'
).get(username) as UserRecord | undefined;
if (!user) {
return res.status(401).json({
success: false,
message: '用户名或密码错误'
});
}
// 2. 检查用户状态
if (!user.enabled) {
return res.status(403).json({
success: false,
message: '账户已被禁用'
});
}
// 3. 验证密码
const validPassword = await bcrypt.compare(password, user.password);
if (!validPassword) {
return res.status(401).json({
success: false,
message: '用户名或密码错误'
});
}
// 4. 生成 JWT Token
const accessToken = jwt.sign(
{
id: user.id,
username: user.username,
role: user.role,
email: user.email
},
env.JWT_SECRET,
{ expiresIn: env.JWT_EXPIRES_IN } as SignOptions
);
const refreshToken = jwt.sign(
{ id: user.id, type: 'refresh' },
env.JWT_SECRET,
{ expiresIn: '7d' } as SignOptions
);
// 5. 更新登录时间
db.prepare(
'UPDATE users SET updated_at = CURRENT_TIMESTAMP WHERE id = ?'
).run(user.id);
// 6. 记录审计日志
db.prepare(`
INSERT INTO audit_logs (id, user_id, action, resource_type,
resource_id, details, ip_address, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
`).run(
randomUUID(),
user.id,
'login',
'auth',
'login',
JSON.stringify({ username }),
req.ip
);
// 7. 返回结果
res.json({
success: true,
message: '登录成功',
data: {
token: accessToken,
refreshToken,
user: {
id: user.id,
username: user.username,
email: user.email,
role: user.role
}
}
});
} catch (error) {
logger.error('登录失败', error);
res.status(500).json({
success: false,
message: '服务器错误'
});
}
});6.4.3 登录流程图解
POST /api/auth/login
{ "username": "admin", "password": "password123" }
↓
1. SELECT * FROM users WHERE username = 'admin'
↓
用户存在?
├─ 否 → 401 "用户名或密码错误"
└─ 是 → 继续
↓
2. 检查 user.enabled
↓
启用?
├─ 否 → 403 "账户已被禁用"
└─ 是 → 继续
↓
3. bcrypt.compare('password123', '$2a$12$...')
↓
密码正确?
├─ 否 → 401 "用户名或密码错误"(不暴露具体原因)
└─ 是 → 继续
↓
4. jwt.sign({ id, username, role, email }, JWT_SECRET, { expiresIn: '24h' })
↓
5. UPDATE users SET updated_at = CURRENT_TIMESTAMP WHERE id = ?
↓
6. INSERT INTO audit_logs ... (记录登录行为)
↓
7. 返回 JSON:
{
"success": true,
"data": {
"token": "eyJhbGci...",
"refreshToken": "eyJhbGci...",
"user": { "id": "...", "username": "admin", ... }
}
}6.4.4 服务层重构
当业务逻辑变得复杂时,应该将逻辑从路由中提取到服务层:
重构前(逻辑在路由中):
// 路由文件中有太多业务逻辑
router.post('/login', async (req, res) => {
const { username, password } = req.body;
const user = db.prepare('...').get(username);
// ... 30+ 行业务逻辑
});重构后(逻辑在服务层):
// backend/src/services/authService.ts
export const authService = {
async login(username: string, password: string, ip?: string) {
const user = db.prepare(
'SELECT * FROM users WHERE username = ?'
).get(username) as UserRecord | undefined;
if (!user || !user.enabled) {
throw new Error('用户名或密码错误');
}
const validPassword = await bcrypt.compare(password, user.password);
if (!validPassword) {
throw new Error('用户名或密码错误');
}
const accessToken = jwt.sign({ ... }, env.JWT_SECRET, { expiresIn: '24h' });
const refreshToken = jwt.sign({ ... }, env.JWT_SECRET, { expiresIn: '7d' });
db.prepare('UPDATE users SET updated_at = CURRENT_TIMESTAMP WHERE id = ?')
.run(user.id);
db.prepare('INSERT INTO audit_logs ...').run(...);
return {
token: accessToken,
refreshToken,
user: { id: user.id, username: user.username, ... }
};
}
};
// 路由文件变得很简洁
router.post('/login', async (req, res) => {
try {
const { username, password } = req.body;
const result = await authService.login(username, password, req.ip);
res.json({ success: true, data: result });
} catch (error) {
if (error.message === '用户名或密码错误') {
return res.status(401).json({ success: false, message: error.message });
}
res.status(500).json({ success: false, message: '服务器错误' });
}
});6.5 数据库操作详解
6.5.1 better-sqlite3 基础操作
项目使用 better-sqlite3,这是一个同步的 SQLite 驱动:
import Database from 'better-sqlite3';
// 打开数据库
const db = new Database('./data/itops.db');
// 查询单行
const user = db.prepare('SELECT * FROM users WHERE id = ?').get(1);
// 查询多行
const servers = db.prepare('SELECT * FROM servers').all();
// 插入数据
const result = db.prepare(
'INSERT INTO servers (id, name, host, port) VALUES (?, ?, ?, ?)'
).run('uuid-1', 'prod-server', '192.168.1.1', 22);
console.log(result.lastInsertRowid); // 插入行的 ID
console.log(result.changes); // 受影响的行数
// 更新数据
db.prepare('UPDATE servers SET status = ? WHERE id = ?').run('online', 'uuid-1');
// 删除数据
db.prepare('DELETE FROM servers WHERE id = ?').run('uuid-1');6.5.2 参数化查询(防 SQL 注入)
永远不要拼接 SQL 字符串!
// 错误做法(SQL 注入风险)
const user = db.prepare(
`SELECT * FROM users WHERE username = '${username}'`
).get();
// 正确做法(参数化查询)
const user = db.prepare(
'SELECT * FROM users WHERE username = ?'
).get(username);
// 多个参数
const result = db.prepare(
'SELECT * FROM tasks WHERE user_id = ? AND status = ? ORDER BY created_at DESC'
).all(userId, status);
// 命名参数(可读性更好)
const result = db.prepare(
'SELECT * FROM tasks WHERE user_id = $userId AND status = $status'
).all({ $userId: userId, $status: status });6.5.3 事务处理
当需要执行多个数据库操作,且要么全部成功、要么全部失败时,使用事务:
// 创建事务
const createServer = db.transaction((data: CreateServerData) => {
// 1. 插入服务器记录
const serverResult = db.prepare(
'INSERT INTO servers (id, name, host, port, created_at) VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP)'
).run(data.id, data.name, data.host, data.port);
// 2. 记录审计日志
db.prepare(
'INSERT INTO audit_logs (id, user_id, action, resource_type, resource_id, details, created_at) VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)'
).run(randomUUID(), data.userId, 'create', 'server', data.id, JSON.stringify(data), new Date().toISOString());
// 如果任何一步失败,整个事务回滚
return serverResult.lastInsertRowid;
});
// 执行事务
try {
const id = createServer.run({
id: randomUUID(),
name: 'prod-server',
host: '192.168.1.1',
port: 22,
userId: 'admin-id'
});
console.log('服务器创建成功,ID:', id);
} catch (error) {
console.error('服务器创建失败,事务已回滚');
}6.5.4 数据库初始化
项目启动时会自动创建所有表:
// backend/src/models/database.ts 模式
import Database from 'better-sqlite3';
const db = new Database('./data/itops.db');
// 启用 WAL 模式(更好的并发性能)
db.pragma('journal_mode = WAL');
// 开启外键约束
db.pragma('foreign_keys = ON');
// 创建用户表
db.exec(`
CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY,
username TEXT UNIQUE NOT NULL,
password TEXT NOT NULL,
email TEXT,
role TEXT DEFAULT 'user',
enabled INTEGER DEFAULT 1,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`);
// 创建服务器表
db.exec(`
CREATE TABLE IF NOT EXISTS servers (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
host TEXT NOT NULL,
port INTEGER DEFAULT 22,
username TEXT,
password TEXT,
status TEXT DEFAULT 'offline',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`);
// ... 更多表创建
export default db;6.6 日志工具使用
6.6.1 项目日志工具
项目使用自定义的日志工具,支持不同级别的日志输出:
import { logger } from './utils/logger';
// 不同级别的日志
logger.info('服务器启动成功');
logger.warn('配置文件未找到,使用默认配置');
logger.error('数据库连接失败', error);
logger.debug('请求参数:', req.body);
// 带上下文的日志
logger.info('用户登录', {
userId: '123',
username: 'admin',
ip: '192.168.1.100'
});6.6.2 日志级别说明
| 级别 | 用途 | 示例 |
|---|---|---|
debug | 开发调试信息 | 请求参数、中间件执行 |
info | 正常业务流程 | 服务器启动、用户登录 |
warn | 警告信息 | 配置缺失、降级处理 |
error | 错误信息 | 数据库连接失败、API 调用异常 |
6.7 错误处理
6.7.1 统一错误处理
项目使用集中式的错误处理中间件:
// backend/src/middleware/errorHandler.ts
import { Request, Response, NextFunction } from 'express';
import { logger } from '../utils/logger';
// 404 处理
export const notFoundHandler = (req: Request, res: Response) => {
res.status(404).json({
success: false,
error: `Route ${req.method} ${req.originalUrl} not found`
});
};
// 全局错误处理
export const errorHandler = (
err: Error,
req: Request,
res: Response,
_next: NextFunction
) => {
const statusCode = (err as any).statusCode || 500;
const message = err.message || 'Internal Server Error';
// 记录错误日志
logger.error(`${req.method} ${req.originalUrl}`, {
error: message,
stack: err.stack,
traceId: (req as any).traceId
});
// 返回统一格式的错误响应
res.status(statusCode).json({
success: false,
error: message
});
};6.7.2 自定义错误类
项目中可以定义自定义错误类,携带状态码:
// backend/src/types/errors.ts
export class AppError extends Error {
public statusCode: number;
constructor(message: string, statusCode: number) {
super(message);
this.statusCode = statusCode;
this.name = 'AppError';
}
}
// 使用示例
throw new AppError('服务器不存在', 404);
throw new AppError('权限不足', 403);
throw new AppError('请求参数错误', 400);6.8 环境变量管理
6.8.1 环境变量配置
项目通过 utils/env.ts 统一管理环境变量:
// backend/src/utils/env.ts
export const env = {
NODE_ENV: process.env.NODE_ENV || 'development',
PORT: parseInt(process.env.PORT || '3000', 10),
JWT_SECRET: process.env.JWT_SECRET || 'default-secret',
JWT_EXPIRES_IN: process.env.JWT_EXPIRES_IN || '24h',
DOUBAO_API_KEY: process.env.DOUBAO_API_KEY || '',
OPENAI_API_KEY: process.env.OPENAI_API_KEY || '',
ALLOWED_ORIGINS: (process.env.ALLOWED_ORIGINS || '*').split(',').map(s => s.trim()),
};6.8.2 环境变量使用
import { env } from './utils/env';
// 使用环境变量
const port = env.PORT;
const jwtSecret = env.JWT_SECRET;
// 条件判断
if (env.NODE_ENV === 'production') {
// 生产环境逻辑
}6.9 项目规范总结
6.9.1 路由编写规范
// 标准路由模板
import { Router, Request, Response } from 'express';
import { db } from '../models/database';
const router = Router();
// 使用 try-catch 包裹所有异步操作
router.get('/', async (req: Request, res: Response) => {
try {
// 1. 获取参数
const { param1, param2 } = req.query;
// 2. 参数验证
if (!param1) {
return res.status(400).json({
success: false,
message: '参数不能为空'
});
}
// 3. 业务逻辑
const result = db.prepare('...').all();
// 4. 返回结果
res.json({
success: true,
data: result
});
} catch (error) {
// 5. 错误处理(交给全局错误处理中间件)
res.status(500).json({
success: false,
message: '服务器错误'
});
}
});
export default router;6.9.2 响应格式规范
项目统一使用以下 JSON 格式:
// 成功响应
{
"success": true,
"data": { ... },
"message": "操作成功"
}
// 错误响应
{
"success": false,
"error": "错误描述",
"message": "详细错误信息"
}6.9.3 HTTP 状态码使用规范
| 状态码 | 含义 | 使用场景 |
|---|---|---|
| 200 | 成功 | 正常 GET、POST 响应 |
| 201 | 已创建 | 成功创建资源(POST) |
| 204 | 无内容 | 成功删除(不需要返回内容) |
| 400 | 请求错误 | 参数验证失败 |
| 401 | 未认证 | Token 缺失或过期 |
| 403 | 权限不足 | 已认证但没有操作权限 |
| 404 | 未找到 | 资源不存在 |
| 500 | 服务器错误 | 内部异常 |
本章小结
本章涵盖了后端开发的完整流程:
- 路由开发:创建路由文件 → 编写路由处理函数 → 注册到应用
- 中间件开发:理解中间件机制,编写认证、权限、追踪等中间件
- 服务层开发:将业务逻辑从路由中提取到服务层,保持路由简洁
- 数据库操作:增删改查、参数化查询、事务处理
- 错误处理:统一格式、自定义错误类、集中处理
- 日志记录:不同级别的日志输出
- 编码规范:响应格式、HTTP 状态码、代码组织
核心原则:路由只管分发,服务只管逻辑,数据库只管存储。
本章练习
基础练习
创建备忘录 CRUD:按照本章示例,完整实现
notes的增删改查功能,并使用 curl 测试每个接口添加认证:为
notes路由添加authenticateToken中间件,确保只有登录用户才能访问添加审计日志:每次创建、更新、删除备忘录时,向
audit_logs表插入一条审计记录
进阶练习
创建服务层:将
notes路由的业务逻辑提取到noteService.ts中,实现路由和逻辑的分离添加分页查询:为
GET /api/notes添加page和pageSize参数,实现分页功能编写自定义错误类:创建
NotFoundError、ValidationError等自定义错误类,在路由中使用
思考题
项目中的
authenticateToken中间件使用了用户缓存(Map),这种设计有什么优势和风险?在什么情况下缓存可能导致问题?为什么项目选择同步的 better-sqlite3 而非异步的 node-sqlite3?如果后端需要同时处理 1000 个请求,会产生什么问题?
路由中直接使用
db.prepare().run()和通过服务层调用,哪种方式更好?为什么?
延伸阅读
本章回顾:你已经掌握了后端开发的基础技能!在下一章中,我们将进入前端开发,学习如何使用 React 构建用户界面。
