闲话少说,开始上代码。
javascript
// ============================================
// 1. 导入Node.js内置模块
// ============================================
const http = require('http'); // HTTP模块:用于创建Web服务器
const url = require('url'); // URL模块:用于解析URL地址
const querystring = require('querystring'); // 查询字符串模块:解析URL参数
const fs = require('fs'); // 文件系统模块:读写文件(可选)
const path = require('path'); // 路径模块:处理文件路径(可选)
// ============================================
// 2. 服务器配置
// ============================================
const PORT = process.env.PORT || 3000; // 端口号:优先使用环境变量,否则用3000
const HOST = 'localhost'; // 主机名:本地开发用localhost
// ============================================
// 3. 路由存储结构
// ============================================
// routes对象用于存储所有注册的路由
// 格式:routes[HTTP方法][URL路径] = 处理函数
const routes = {
'GET': {}, // 存储GET请求的路由
'POST': {}, // 存储POST请求的路由
'PUT': {}, // 存储PUT请求的路由
'DELETE': {} // 存储DELETE请求的路由
};
// ============================================
// 4. 路由注册函数
// ============================================
/**
* 注册一个路由
* @param {string} method - HTTP方法(GET、POST、PUT、DELETE)
* @param {string} path - URL路径(支持 :id 这样的动态参数)
* @param {function} handler - 处理函数,接收(req, res, params)三个参数
*
* 使用示例:
* addRoute('GET', '/api/users', (req, res) => { ... })
* addRoute('GET', '/api/users/:id', (req, res, params) => { ... })
*/
function addRoute(method, path, handler) {
routes[method][path] = handler;
}
// ============================================
// 5. 解析请求体(POST/PUT请求的数据)
// ============================================
/**
* 解析HTTP请求体中的数据
* @param {object} req - HTTP请求对象
* @returns {Promise} 返回解析后的数据
*
* 支持三种格式:
* 1. application/json -> 返回JSON对象
* 2. application/x-www-form-urlencoded -> 返回键值对对象
* 3. 其他格式 -> 返回原始字符串
*/
function parseBody(req) {
return new Promise((resolve, reject) => {
let body = '';
// 监听数据事件:接收请求体数据块
req.on('data', chunk => {
body += chunk.toString(); // 将Buffer转为字符串并拼接
});
// 监听结束事件:所有数据接收完毕
req.on('end', () => {
try {
const contentType = req.headers['content-type'] || '';
// 根据Content-Type解析不同格式的数据
if (contentType.includes('application/json')) {
// JSON格式:{"name":"张三","age":25}
resolve(JSON.parse(body));
} else if (contentType.includes('application/x-www-form-urlencoded')) {
// 表单格式:name=张三&age=25
resolve(querystring.parse(body));
} else {
// 其他格式:直接返回字符串
resolve(body);
}
} catch (error) {
reject(error); // 解析失败,返回错误
}
});
// 监听错误事件
req.on('error', reject);
});
}
// ============================================
// 6. 响应工具函数
// ============================================
/**
* 统一发送JSON格式的响应
* @param {object} res - HTTP响应对象
* @param {number} statusCode - HTTP状态码(200、404、500等)
* @param {object} data - 要返回的数据(会自动转为JSON)
*
* 使用示例:
* sendResponse(res, 200, { code: 0, data: user, message: 'success' })
*/
function sendResponse(res, statusCode, data) {
res.statusCode = statusCode; // 设置状态码
res.setHeader('Content-Type', 'application/json'); // 设置响应头为JSON格式
res.end(JSON.stringify(data)); // 将数据转为JSON并发送
}
// ============================================
// 7. 定义具体的路由处理函数
// ============================================
// 7.1 GET /api/users - 获取所有用户列表
addRoute('GET', '/api/users', async (req, res) => {
// 模拟数据库中的用户数据
const users = [
{ id: 1, name: '张三', age: 25 },
{ id: 2, name: '李四', age: 30 },
{ id: 3, name: '王五', age: 28 }
];
// 返回成功响应
sendResponse(res, 200, {
code: 0, // 业务状态码:0表示成功
data: users, // 实际数据
message: 'success'
});
});
// 7.2 GET /api/users/:id - 根据ID获取单个用户
addRoute('GET', '/api/users/:id', async (req, res, params) => {
// 从URL参数中获取ID并转为数字
const id = parseInt(params.id);
// 模拟数据库中的用户数据
const users = [
{ id: 1, name: '张三', age: 25 },
{ id: 2, name: '李四', age: 30 },
{ id: 3, name: '王五', age: 28 }
];
// 在数组中查找ID匹配的用户
const user = users.find(u => u.id === id);
if (user) {
// 找到用户:返回200成功
sendResponse(res, 200, {
code: 0,
data: user,
message: 'success'
});
} else {
// 没找到用户:返回404未找到
sendResponse(res, 404, {
code: 404,
message: '用户不存在'
});
}
});
// 7.3 POST /api/users - 创建新用户
addRoute('POST', '/api/users', async (req, res) => {
try {
// 解析请求体中的JSON数据
const body = await parseBody(req);
const { name, age } = body;
// 验证必填参数
if (!name || !age) {
return sendResponse(res, 400, {
code: 400,
message: '缺少必要参数: name, age'
});
}
// 创建新用户(使用时间戳作为ID)
const newUser = {
id: Date.now(), // 用时间戳作为唯一ID
name,
age: parseInt(age)
};
// 返回创建成功(201状态码)
sendResponse(res, 201, {
code: 0,
data: newUser,
message: '创建成功'
});
} catch (error) {
// JSON解析失败,返回400错误
sendResponse(res, 400, {
code: 400,
message: '参数格式错误,请使用JSON格式'
});
}
});
// ============================================
// 8. 404处理函数(路由未匹配)
// ============================================
/**
* 处理未匹配的路由,返回404错误
*/
function handleNotFound(req, res) {
sendResponse(res, 404, {
code: 404,
message: '接口不存在'
});
}
// ============================================
// 9. 创建HTTP服务器
// ============================================
const server = http.createServer(async (req, res) => {
// 9.1 设置CORS跨域响应头
res.setHeader('Access-Control-Allow-Origin', '*'); // 允许所有域名访问
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); // 允许的HTTP方法
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization'); // 允许的请求头
// 9.2 处理OPTIONS预检请求
// 浏览器在发送复杂请求前会先发送OPTIONS请求,这里直接返回200
if (req.method === 'OPTIONS') {
res.statusCode = 200;
res.end();
return;
}
try {
// 9.3 解析请求URL
const parsedUrl = url.parse(req.url, true); // true表示解析查询参数
const pathname = parsedUrl.pathname; // 获取路径部分,如 /api/users/1
const method = req.method; // 获取HTTP方法,如 GET
// 9.4 记录请求日志
console.log(`${new Date().toISOString()} - ${method} ${pathname}`);
// 9.5 精确匹配路由
// 检查是否有完全匹配的路由,如 '/api/users'
if (routes[method] && routes[method][pathname]) {
await routes[method][pathname](req, res);
return;
}
// 9.6 动态参数匹配路由(支持 :id 这种格式)
// 遍历所有已注册的路由,尝试匹配带参数的路由
const routeKeys = Object.keys(routes[method] || {});
for (const route of routeKeys) {
// 将路由路径和请求路径都按 '/' 分割成数组
const routeParts = route.split('/'); // ['', 'api', 'users', ':id']
const pathParts = pathname.split('/'); // ['', 'api', 'users', '1']
// 只有路径长度相同才可能匹配
if (routeParts.length === pathParts.length) {
const params = {}; // 存储提取的动态参数
let match = true; // 是否匹配的标志
// 逐个比较路径的每一部分
for (let i = 0; i < routeParts.length; i++) {
if (routeParts[i].startsWith(':')) {
// 如果路由部分以 ':' 开头,说明是动态参数
// 把对应的值存入params对象
params[routeParts[i].slice(1)] = pathParts[i];
// 例如:':id' -> '1',存入 params.id = '1'
} else if (routeParts[i] !== pathParts[i]) {
// 如果是静态部分但不匹配,则匹配失败
match = false;
break;
}
}
// 如果所有部分都匹配成功,调用对应的处理函数
if (match) {
await routes[method][route](req, res, params);
return;
}
}
}
// 9.7 没有匹配到任何路由,返回404
handleNotFound(req, res);
} catch (error) {
// 9.8 捕获所有未处理的错误,返回500
console.error('服务器错误:', error);
sendResponse(res, 500, {
code: 500,
message: '服务器内部错误'
});
}
});
// ============================================
// 10. 启动服务器
// ============================================
server.listen(PORT, () => {
console.log(`✅ 服务器运行在 http://${HOST}:${PORT}`);
console.log(`📋 可用接口:`);
console.log(` GET http://${HOST}:${PORT}/api/users`);
console.log(` GET http://${HOST}:${PORT}/api/users/:id`);
console.log(` POST http://${HOST}:${PORT}/api/users`);
console.log(`\n💡 提示: 按 Ctrl+C 停止服务器`);
});
// ============================================
// 11. 优雅关闭(处理进程终止信号)
// ============================================
/**
* 当收到 SIGTERM 信号时(如使用PM2停止),优雅地关闭服务器
*/
process.on('SIGTERM', () => {
console.log('收到终止信号,正在关闭服务器...');
server.close(() => {
console.log('服务器已关闭');
process.exit(0); // 退出进程
});
});
// 处理 Ctrl+C 强制退出
process.on('SIGINT', () => {
console.log('\n收到中断信号,正在关闭服务器...');
server.close(() => {
console.log('服务器已关闭');
process.exit(0);
});
});