const http = require("http");
const { emitWarning } = require("process");
const qs = require("qs");
const server = http.createServer((req, res) => {
console.log(req.method); // GET
const url = req.url; // 获取请求的完整 url
console.log(url);
req.query = qs.parse(url.split("?")[1]); // qs 解析 query 为 JS 对象
res.end(JSON.stringify(req.query)); // 消息发送已完成
});
server.listen(8000, () => {
console.log("listen on 8000");
});
运行输入 http://localhost:8000/?a=100&b=erere 后,后台输出:
listen on 8000
GET
/?a=100&b=erere
GET
/favicon.ico
/favicon.ico 是浏览器自动会发送的 GET 请求。
浏览器显示结果:
const http = require("http");
const server = http.createServer((req, res) => {
if (req.method === "POST") {
// 请求的数据格式
console.log("content-type", req.headers["content-type"]);
// 接收数据
let postData = "";
// 接收数据流,req 发送数据时触发
req.on("data", (chunk) => {
postData += chunk.toString();
});
// req 传输完成时触发
req.on("end", () => {
console.log(postData);
res.end("hello world"); // 在这里返回,因为是异步
});
}
});
server.listen(8000, () => {
console.log("listen on port 8000");
});
用 postman 发送请求结果:
const http = require("http");
const qs = require("qs");
const server = http.createServer((req, res) => {
const method = req.method;
const url = req.url;
const path = url.split("?")[0];
const query = qs.parse(url.split("?")[1]);
// 设置返回格式为 JSON
res.setHeader("content-type", "application/json");
// 返回的数据
const resData = {
method,
url,
path,
query,
};
// 返回
if (method === "GET") {
// 返回 JSON 格式的字符串
res.end(JSON.stringify(resData));
}
if (method === "POST") {
let postData = "";
req.on("data", (chunk) => {
postData += chunk.toString();
});
req.on("end", () => {
resData.postData = postData;
res.end(JSON.stringify(resData));
});
}
});
server.listen(8000, () => {
console.log("listen on port 8000");
});
GET 返回结果:
POST 返回结果:
yarn init -y
node 项目初始化
git init
初始化 Git 仓库
安装所需第三方库
yarn add nodemon cross-env --dev
在 package.json
里编写 scripts,简化输入的命令行指令
"scripts": {
"dev": "cross-env NODE_ENV=dev nodemon ./bin/www.js"
},
这样写完后,在命令行里 yarn dev
或者 npm run dev
就可以代执行上面的命令。
cross-env NODE_ENV=dev
当前的环境为开发环境,并且有一个变量为 NODE_ENV 值为 dev。js 文件可以通过 process.env.NODE_ENV
来获取到 dev。nodemon ./bin/www.js
nodemon 监听 www.js 的变化,每次变化就重新自动执行。(因为自己自定义了主入口为 www.js)在 ./bin/www.js 里写如下代码:
const http = require("http");
const { serverHandle } = require("../app");
const PORT = 8000;
// 创建服务器后的回调放在 app.js 文件里
const server = http.createServer(serverHandle);
// 在 8000 端口上监听
server.listen(PORT);
在 app.js 文件里写创建服务器的回调,app.js 专注于业务代码。
const serverHandle = (req, res) => {
// 设置返回格式 JSON
res.setHeader("Content-type", "application/json");
const resData = {
name: "sjh",
site: "ssjjhh.com",
env: process.env.NODE_ENV,
};
res.end(JSON.stringify(resData));
};
module.exports = {
serverHandle,
};
执行 yarn dev
,访问本地的 8000 端口,可以得到服务器返回的 JSON 数据。
通过查看路由,可以发现接口分成两大类:blog 和 user。可以通过两个文件实现六个接口。
先跑通是最重要的,把每个接口先写一下,返回的内容先不管。
const handleBlogRouter = (req, res) => {
const method = req.method;
// 获取博客列表
if (method === "GET" && req.path === "/api/blog/list") {
return {
msg: "这是获取博客列表的接口",
};
}
// 获取博客详情
if (method === "GET" && req.path === "/api/blog/detail") {
return {
msg: "这是获取博客详情的接口",
};
}
// 新建一篇博客
if (method === "POST" && req.path === "/api/blog/new") {
return {
msg: "这是新建博客的接口",
};
}
// 更新一篇博客
if (method === "POST" && req.path === "/api/blog/update") {
return {
msg: "这是更新博客的接口",
};
}
// 删除一篇博客
if (method === "POST" && req.path === "/api/blog/del") {
return {
msg: "这是删除博客的接口",
};
}
};
module.exports = {
handleBlogRouter,
};
user 路由同理
const handleUserRouter = (req, res) => {
const method = req.method;
// 登录
if (method === "POST" && req.path === "/api/user/login") {
return {
msg: "这是用户登录接口",
};
}
};
module.exports = {
handleUserRouter,
};
将两个路由引入 app.js,并经过解析得到 path 和 query 后,传递给路由。如果请求的路径路由能匹配上的话,就会返回数据,否则不返回。
意义在于,将业务功能分离,提高可维护性。
根据这个原理,如果路由都没匹配上,就是 404 未找到了。在 serverHandle 末尾进行 404 的处理。
const qs = require("qs");
const { handleBlogRouter } = require("./src/router/blog");
const { handleUserRouter } = require("./src/router/user");
const serverHandle = (req, res) => {
// 设置返回格式 JSON
res.setHeader("Content-type", "application/json");
// 获取 path
const url = req.url;
req.path = url.split("?")[0];
// 解析 query
req.query = qs.parse(req.path)
// 处理 blog 路由
const blogData = handleBlogRouter(req, res);
if (blogData) {
res.end(JSON.stringify(blogData));
// 需 return,否则会继续往下执行
return;
}
// 处理 user 路由
const userData = handleUserRouter(req, res);
if (userData) {
res.end(JSON.stringify(userData));
return;
}
// 未命中路由:纯文本返回 404 信息
res.writeHead(404, { "content-type": "text/plain" });
res.write("404 not found");
res.end();
};
module.exports = {
serverHandle,
};
创建 model 文件夹,创建成功和失败模型:
// src/model/resModel.js
class BaseModel {
constructor(data, message) {
// data 可以是对象,也可以是字符串
if (typeof data === "string") {
this.message = data;
data = null;
message = null;
}
if (data) {
this.data = data;
}
if (message) {
this.message = message;
}
}
}
/**
* 成功信息的模型
*/
class SuccessModel extends BaseModel {
constructor(data, message) {
super(data, message);
this.errno = 0;
}
}
/**
* 失败信息的模型
*/
class ErrorModel extends BaseModel {
constructor(data, message) {
super(data, message);
this.errno = -1;
}
}
module.exports = {
SuccessModel,
ErrorModel,
};
以后要返回信息,就经过模型的加工,返回的 json 结构预期如下:
{
"errno": 0,
"data": {...},
"message": "xxx"
}
现在建一个 controller 层,里边写相关的业务。因为还没连接数据库,因此先暂时返回自己造的数据。下面是获取博客列表数据的相关代码:
// src/controller/blog.js
const getList = (author, keyword) => {
// 返回 Mock 数据(格式是正确的)
return [
{
id: 1,
title: "标题A",
content: "内容A",
createTime: 1654087871762,
author: "zhangsan",
},
{
id: 2,
title: "标题B",
content: "内容B",
createTime: 1654087879762,
author: "lisi",
},
];
};
controller 将数据剥离后,router 里只专注数据。下面是博客列表路由的相关代码:
// src/router/blog.js
// 获取博客列表
if (method === "GET" && req.path === "/api/blog/list") {
const author = req.query.author || "";
const keyword = req.query.keyword || "";
const listData = getList(author, keyword);
return new SuccessModel(listData);
}
返回结果:
// src/router/blog.js
// 获取博客详情
if (method === "GET" && req.path === "/api/blog/detail") {
const id = req.query.id;
const data = getDetail(id);
return new SuccessModel(data);
}
controller 和返回结果略。
app.js 里现在只有解析 path 和 query,POST data 并没有。POST 数据传输是异步的,要特意等 POST 数据传输结束后,路由的相关代码才能运行,确保拿到 POST data。
因为了方便,单独创建一个 处理 POST data 的函数。异步的方法返回 promise 后,可以使用 then/catch 会标或者 await 语法糖来处理异步请求后,才跑下面的代码。
// app.js
const getPostData = (req) => {
return new Promise((resolve, reject) => {
// 非 POST 请求不存在 POST data 的问题
if (req.method !== "POST") {
resolve({});
return;
}
// 如果 POST data 不是 JSON 格式的数据,直接忽略(本项目的 POST data 都是 JSON 格式)
if (req.headers["content-type"] !== "application/json") {
resolve({});
return;
}
let postData = "";
req.on("data", (chunk) => {
postData += chunk.toString();
});
req.on("end", () => {
if (!postData) {
resolve({});
return;
}
resolve(JSON.parse(postData));
});
});
};
const serverHandle = async (req, res) => {
// 设置返回格式 JSON
res.setHeader("Content-type", "application/json");
// 获取 path
const url = req.url;
req.path = url.split("?")[0];
// 解析 query
req.query = qs.parse(req.path);
// 解析 POST data 后放在 req.body 内
const postData = await getPostData(req);
req.body = postData;
// 处理 blog 路由
const blogData = handleBlogRouter(req, res);
if (blogData) {
res.end(JSON.stringify(blogData));
// 需 return,否则会继续往下执行
return;
}
// 处理 user 路由
const userData = handleUserRouter(req, res);
if (userData) {
res.end(JSON.stringify(userData));
return;
}
// 未命中路由:纯文本返回 404 信息
res.writeHead(404, { "content-type": "text/plain" });
res.write("404 not found");
res.end();
};
有了上面的 postData 处理后,路由可以通过 req.body
获取到 post 传递过来的数据了。
// router/blog.js
// 新建一篇博客
if (method === "POST" && req.path === "/api/blog/new") {
const data = newBlog(req.body);
return new SuccessModel(data);
}
// controller/blog.js
const newBlog = (blogData = {}) => {
// blogData 是一个博客对象,包含 title、content 属性
return {
...blogData, // 只是演示 POST data 成功获取了
id: 3, // 表示新建博客,插入到数据表里面的 id
};
};
在 postman 发送 post 请求以及响应结果:
更新路由也是用 post 方法,但是和新建博客不一样的地方是,更新博客时需要携带 id 参数才能进行修改。
// controller/blog.js
/**
* 更新指定 id 的博客内容
* @param {number} id 要更新博客的对应 id
* @param {object} blogData 博客对象,包含 title、content 属性
*/
const updateBlog = (id, blogData = {}) => {
console.log("update blog", blogData);
return true;
};
// router/blog.js
// 更新一篇博客
if (method === "POST" && req.path === "/api/blog/update") {
const result = updateBlog(id, req.body);
if (result) {
return new SuccessModel();
} else {
return new ErrorModel("更新失败");
}
}
// controller/blog.js
const delBlog = (id) => {
return true;
};
// router/blog.js
// 删除一篇博客
if (method === "POST" && req.path === "/api/blog/del") {
const result = delBlog(id);
if (result) {
return new SuccessModel();
} else {
return new ErrorModel("删除博客失败");
}
}
// controller/user.js
const loginCheck = (username, password) => {
// 先使用假数据
if (username === "admin" && password === "123456") {
return true;
}
return false;
};
module.exports = {
loginCheck
};
// router/user.js
const { loginCheck } = require("../controller/user");
const { SuccessModel, ErrorModel } = require("../model/resModel");
const handleUserRouter = (req, res) => {
const method = req.method;
// 登录
if (method === "POST" && req.path === "/api/user/login") {
const { username, password } = req.body;
const result = loginCheck(username, password);
if (result) {
return new SuccessModel();
}
return new ErrorModel("登录失败");
}
};
module.exports = {
handleUserRouter,
};