一、实战项目一:用户认证系统

1.1 项目概述

实现一个完整的用户认证系统,包含:

  • 用户注册/登录
  • JWT Token 认证
  • 密码加密(bcrypt)
  • 权限校验中间件

1.2 项目结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
auth-system/
├── config/
│ └── jwt.js
├── controllers/
│ └── authController.js
├── middleware/
│ └── authMiddleware.js
├── models/
│ └── User.js
├── routes/
│ └── auth.js
├── utils/
│ └── hash.js
├── app.js
└── package.json

1.3 安装依赖

1
2
3
npm init -y
npm install express bcryptjs jsonwebtoken
npm install --save-dev nodemon

1.4 核心代码实现

utils/hash.js(密码加密工具)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const bcrypt = require('bcryptjs');

// 加密密码
async function hashPassword(password) {
const salt = await bcrypt.genSalt(10);
return await bcrypt.hash(password, salt);
}

// 验证密码
async function comparePassword(password, hashedPassword) {
return await bcrypt.compare(password, hashedPassword);
}

module.exports = { hashPassword, comparePassword };

config/jwt.js(JWT 配置)

1
2
3
4
module.exports = {
secret: 'your-secret-key-change-in-production',
expiresIn: '7d' // 7天过期
};

models/User.js(用户模型)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 内存存储(生产环境应使用数据库)
let users = [];
let nextId = 1;

module.exports = {
findAll: () => [...users],
findById: (id) => users.find(u => u.id === parseInt(id)),
findByEmail: (email) => users.find(u => u.email === email),
create: (userData) => {
const user = {
id: nextId++,
...userData,
createdAt: new Date()
};
users.push(user);
return user;
},
update: (id, userData) => {
const index = users.findIndex(u => u.id === parseInt(id));
if (index === -1) return null;
users[index] = { ...users[index], ...userData };
return users[index];
}
};

controllers/authController.js(认证控制器)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
const User = require('../models/User');
const { hashPassword, comparePassword } = require('../utils/hash');
const jwt = require('jsonwebtoken');
const { secret, expiresIn } = require('../config/jwt');

// 用户注册
exports.register = async (req, res, next) => {
try {
const { username, email, password } = req.body;

// 检查邮箱是否已注册
if (User.findByEmail(email)) {
return res.status(400).json({ error: '邮箱已被注册' });
}

// 加密密码
const hashedPassword = await hashPassword(password);

// 创建用户
const user = User.create({
username,
email,
password: hashedPassword
});

// 生成 token
const token = jwt.sign({ userId: user.id }, secret, { expiresIn });

res.status(201).json({
message: '注册成功',
user: { id: user.id, username, email },
token
});
} catch (error) {
next(error);
}
};

// 用户登录
exports.login = async (req, res, next) => {
try {
const { email, password } = req.body;

// 查找用户
const user = User.findByEmail(email);
if (!user) {
return res.status(401).json({ error: '邮箱或密码错误' });
}

// 验证密码
const isMatch = await comparePassword(password, user.password);
if (!isMatch) {
return res.status(401).json({ error: '邮箱或密码错误' });
}

// 生成 token
const token = jwt.sign({ userId: user.id }, secret, { expiresIn });

res.json({
message: '登录成功',
user: { id: user.id, username: user.username, email: user.email },
token
});
} catch (error) {
next(error);
}
};

// 获取当前用户信息
exports.getMe = (req, res) => {
const user = User.findById(req.userId);
if (!user) {
return res.status(404).json({ error: '用户不存在' });
}
res.json({
id: user.id,
username: user.username,
email: user.email,
createdAt: user.createdAt
});
};

middleware/authMiddleware.js(认证中间件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const jwt = require('jsonwebtoken');
const { secret } = require('../config/jwt');

module.exports = (req, res, next) => {
// 获取 token
const authHeader = req.headers['authorization'];
const token = authHeader?.split(' ')[1]; // Bearer <token>

if (!token) {
return res.status(401).json({ error: '请提供认证令牌' });
}

try {
// 验证 token
const decoded = jwt.verify(token, secret);
req.userId = decoded.userId;
next();
} catch (error) {
return res.status(401).json({ error: '令牌无效或已过期' });
}
};

routes/auth.js(路由)

1
2
3
4
5
6
7
8
9
10
11
12
13
const express = require('express');
const router = express.Router();
const authController = require('../controllers/authController');
const authMiddleware = require('../middleware/authMiddleware');

// 公开路由
router.post('/register', authController.register);
router.post('/login', authController.login);

// 需要认证的路由
router.get('/me', authMiddleware, authController.getMe);

module.exports = router;

app.js(应用入口)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const express = require('express');
const morgan = require('morgan');
const bodyParser = require('body-parser');

const authRoutes = require('./routes/auth');

const app = express();

// 中间件
app.use(morgan('dev'));
app.use(bodyParser.json());

// 路由
app.use('/api/auth', authRoutes);

// 404 处理
app.use((req, res) => {
res.status(404).json({ error: 'Not Found' });
});

// 错误处理
app.use((err, req, res, next) => {
console.error('错误:', err);
res.status(500).json({ error: '服务器内部错误' });
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`服务器运行在 http://localhost:${PORT}`);
});

1.5 测试接口

1
2
3
4
5
6
7
8
9
10
11
12
13
# 注册用户
curl -X POST http://localhost:3000/api/auth/register \
-H "Content-Type: application/json" \
-d '{"username":"张三","email":"zhangsan@example.com","password":"123456"}'

# 登录
curl -X POST http://localhost:3000/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"zhangsan@example.com","password":"123456"}'

# 获取用户信息(需要 token)
curl http://localhost:3000/api/auth/me \
-H "Authorization: Bearer <your_token>"

二、实战项目二:RESTful API 服务

2.1 项目概述

构建一个完整的 RESTful API 服务,包含:

  • 用户、文章、评论三个资源
  • CRUD 操作
  • 分页、筛选、排序
  • API 文档

2.2 RESTful API 设计规范

HTTP 方法 路径 功能
GET /api/users 获取用户列表
POST /api/users 创建用户
GET /api/users/:id 获取单个用户
PUT /api/users/:id 更新用户
DELETE /api/users/:id 删除用户

2.3 项目结构

1
2
3
4
5
6
7
8
9
10
11
12
restful-api/
├── controllers/
│ ├── userController.js
│ └── postController.js
├── models/
│ ├── User.js
│ └── Post.js
├── routes/
│ ├── users.js
│ └── posts.js
├── app.js
└── package.json

2.4 核心实现

详细的代码实现请参考第 06 章 Express 框架实战中的 RESTful API 示例。

三、实战项目三:网络爬虫

3.1 项目概述

使用 Node.js 实现一个简单的网络爬虫:

  • 使用 axios 发送 HTTP 请求
  • 使用 cheerio 解析 HTML
  • 数据清洗与存储
  • 定时任务执行

3.2 安装依赖

1
2
npm init -y
npm install axios cheerio

3.3 基础爬虫实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// crawler.js
const axios = require('axios');
const cheerio = require('cheerio');
const fs = require('fs').promises;

// 爬取目标网站
async function crawlWebsite(url) {
try {
// 发送请求
const response = await axios.get(url, {
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
},
timeout: 10000
});

// 解析 HTML
const $ = cheerio.load(response.data);

// 提取数据(示例:爬取文章标题和链接)
const articles = [];
$('.article-item').each((index, element) => {
const $element = $(element);
articles.push({
title: $element.find('.title').text().trim(),
url: $element.find('a').attr('href'),
author: $element.find('.author').text().trim(),
publishedAt: $element.find('.date').text().trim()
});
});

return articles;
} catch (error) {
console.error('爬取失败:', error.message);
throw error;
}
}

// 保存数据到 JSON 文件
async function saveToFile(data, filename) {
try {
await fs.writeFile(filename, JSON.stringify(data, null, 2), 'utf-8');
console.log(`数据已保存到 ${filename}`);
} catch (error) {
console.error('保存失败:', error.message);
}
}

// 主函数
async function main() {
const url = 'https://example.com/articles';
console.log('开始爬取...');

const articles = await crawlWebsite(url);
console.log(`爬取到 ${articles.length} 篇文章`);

await saveToFile(articles, 'articles.json');
}

main().catch(console.error);

3.4 使用 Puppeteer 爬取动态页面

1
npm install puppeteer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
const puppeteer = require('puppeteer');

async function crawlDynamicPage() {
// 启动浏览器
const browser = await puppeteer.launch({
headless: 'new', // 无头模式
args: ['--no-sandbox', '--disable-setuid-sandbox']
});

try {
const page = await browser.newPage();

// 设置视口大小
await page.setViewport({ width: 1920, height: 1080 });

// 访问页面
await page.goto('https://example.com', {
waitUntil: 'networkidle2' // 等待网络空闲
});

// 等待元素加载
await page.waitForSelector('.article-list');

// 提取数据
const articles = await page.evaluate(() => {
const items = document.querySelectorAll('.article-item');
return Array.from(items).map(item => ({
title: item.querySelector('.title')?.textContent?.trim(),
url: item.querySelector('a')?.href,
summary: item.querySelector('.summary')?.textContent?.trim()
}));
});

console.log('爬取结果:', articles);

// 截图(调试用)
await page.screenshot({ path: 'screenshot.png', fullPage: true });

return articles;
} finally {
await browser.close();
}
}

crawlDynamicPage().catch(console.error);

3.5 定时任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const { crawlWebsite, saveToFile } = require('./crawler');

// 使用 setInterval 实现定时任务
function startScheduler(intervalMinutes = 60) {
console.log(`定时任务已启动,每 ${intervalMinutes} 分钟执行一次`);

// 立即执行一次
runCrawl();

// 定时执行
setInterval(runCrawl, intervalMinutes * 60 * 1000);
}

async function runCrawl() {
const timestamp = new Date().toISOString();
console.log(`[${timestamp}] 开始爬取...`);

try {
const articles = await crawlWebsite('https://example.com');
const filename = `data/articles_${Date.now()}.json`;
await saveToFile(articles, filename);
console.log(`[${timestamp}] 爬取完成,共 ${articles.length} 条数据`);
} catch (error) {
console.error(`[${timestamp}] 爬取失败:`, error.message);
}
}

// 启动定时任务
startScheduler(30); // 每30分钟执行一次

四、项目部署与运维

4.1 PM2 进程管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 安装 PM2
npm install -g pm2

# 启动应用
pm2 start app.js --name "myapp"

# 查看应用状态
pm2 status

# 查看日志
pm2 logs myapp

# 重启应用
pm2 restart myapp

# 停止应用
pm2 stop myapp

# 开机自启动
pm2 startup
pm2 save

PM2 配置文件(ecosystem.config.js):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module.exports = {
apps: [{
name: 'myapp',
script: 'app.js',
instances: 'max', // 启动多个实例(集群模式)
exec_mode: 'cluster', // 集群模式
env: {
NODE_ENV: 'development',
PORT: 3000
},
env_production: {
NODE_ENV: 'production',
PORT: 80
}
}]
};

4.2 Nginx 反向代理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# /etc/nginx/sites-available/myapp
server {
listen 80;
server_name example.com;

location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}

4.3 环境变量管理

1
2
# 安装 dotenv
npm install dotenv
1
2
3
4
5
6
7
8
9
10
// app.js
require('dotenv').config();

const PORT = process.env.PORT || 3000;
const DB_HOST = process.env.DB_HOST;
const JWT_SECRET = process.env.JWT_SECRET;

app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});

.env 文件:

1
2
3
4
5
6
NODE_ENV=production
PORT=3000
DB_HOST=localhost
DB_USER=root
DB_PASSWORD=your_password
JWT_SECRET=your-secret-key

五、Node.js 最佳实践

5.1 项目目录结构规范

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
project/
├── src/
│ ├── controllers/ # 控制器
│ ├── models/ # 数据模型
│ ├── routes/ # 路由
│ ├── middleware/ # 中间件
│ ├── services/ # 业务逻辑
│ ├── utils/ # 工具函数
│ └── app.js # 应用入口
├── tests/ # 测试文件
├── config/ # 配置文件
├── public/ # 静态资源
├── .env # 环境变量
├── .gitignore
└── package.json

5.2 错误处理规范

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 统一错误处理
class AppError extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
this.isOperational = true;
Error.captureStackTrace(this, this.constructor);
}
}

// 异步错误包装器
const catchAsync = (fn) => {
return (req, res, next) => {
fn(req, res, next).catch(next);
};
};

// 全局错误处理中间件
app.use((err, req, res, next) => {
err.statusCode = err.statusCode || 500;
err.status = err.status || 'error';

res.status(err.statusCode).json({
status: err.status,
message: err.message,
...(process.env.NODE_ENV === 'development' && { stack: err.stack })
});
});

5.3 安全最佳实践

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 安装安全中间件
npm install helmet cors express-rate-limit

const helmet = require('helmet');
const cors = require('cors');
const rateLimit = require('express-rate-limit');

// 安全头设置
app.use(helmet());

// CORS 配置
app.use(cors({
origin: ['https://example.com'],
credentials: true
}));

// 请求限流
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15分钟
max: 100 // 最多100次请求
});
app.use('/api', limiter);

💡 小结:通过这三个实战项目,你已经掌握了 Node.js 开发的核心流程:从需求分析、项目搭建、代码实现到部署上线。接下来可以尝试扩展这些项目,添加更多功能,如邮件通知、文件上传、支付集成等。