一、实战项目一:用户认证系统
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' };
|
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 });
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: '邮箱或密码错误' }); }
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) => { const authHeader = req.headers['authorization']; const token = authHeader?.split(' ')[1];
if (!token) { return res.status(401).json({ error: '请提供认证令牌' }); }
try { 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);
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"}'
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
| 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 });
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; } }
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 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');
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);
|
四、项目部署与运维
4.1 PM2 进程管理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 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
| 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 3 4 5 6 7 8 9 10
| 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());
app.use(cors({ origin: ['https://example.com'], credentials: true }));
const limiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 100 }); app.use('/api', limiter);
|
💡 小结:通过这三个实战项目,你已经掌握了 Node.js 开发的核心流程:从需求分析、项目搭建、代码实现到部署上线。接下来可以尝试扩展这些项目,添加更多功能,如邮件通知、文件上传、支付集成等。