前言:
本文是学习Node中间层实践的相关笔记,推荐大家可以去阅读jsliang大佬的原文Node-从0基础到实战企业官网。开启本文前,可以先去了解一下node的基本知识,会对了解本文内容有所帮助。当然,也可以选择,跟着本文,边读边了解。(PS:继续还债的node小白╮(╯▽╰)╭)
本文重点内容:
- Node 基础 – 通过对Node基础的了解学习,打下Node基础;
- Node API – 开启服务提供API给前端调用;
- Node 连接 MySQL – 通过npm安装mysql,从而实现数据库的连接;
- Node 实战 – 企业官网从0开始,打造能注册、登录以及留言的企业官网;
- Node 部署 – 如何通过部署云服务器,让大家可以查看你的网站。
文章目录- 一、基础学习
- 1.1 HTTP--开始Node之旅
- 1.2 URL模块
- 1.3 CommonJS
- 1.4 fs文件管理
- 1.5 fs案例
- 1.6 fs流
- 1.7 创建web服务器
- 1.8 非阻塞I/O事件驱动
- 1.9 get与post
- 1.9 Node连接MySQL
- 二、Web实战----企业官网
- 2.1 编程环境
- 2.2 后端接口
- 2.3 注册功能
- 2.4 注册功能
- 2.5 留言功能
- 三、工具整合
- 3.1 supervisor -- 监听Node改动
- 3.2 PM2 -- Node进程管理
- 参考资料
http.js的相关代码:
// 1. 引入 http 模块
var http = require("http");
// 2. 用 http 模块创建服务
http.createServer(function (req, res) {
// 设置 HTTP 头部,状态码是 200,文件类型是 html,字符集是 utf8
res.writeHead(200, {
"Content-Type": "text/html;charset=UTF-8"
});
// 往页面打印值
res.write('Hello NodeJS');
// 结束响应
res.end();
}).listen(3000); // 监听的端口
那么,上面的代码,要怎么用呢?
- 首先,将上面的代码复制粘贴到http.js中;
- 然后,启动VS Code终端: Ctrl + ~ ;
- 接着,输入node http.js 并回车;
- 最后,打开localhost:3000
好的,就可以了,现在,讲解一下上面的代码。
首先,需要开启HTTP模式。一般,像PHP这类老牌子的后端语言,需要Apache或者Nginx开启HTTP服务。然而,我们的Node不需要:
var http = require("http");
然后,开启HTTP服务,并设置开启的端口:
http.createServer(function (req, res) {
// ... 步骤 3 代码
}).listen(3000); // 监听的端口
接着,设置HTTP头部,并往页面打印值,最后结束响应:
// 设置 HTTP 头部,状态码是 200,文件类型是 html,字符集是 utf8
res.writeHead(200, {
"Content-Type": "text/html;charset=UTF-8"
});
// 往页面打印值
res.write('Hello NodeJS');
// 结束响应
res.end();
最后,往游览器输入http://localhost:3000/ ,将访问到我们开启的Node服务,从而往页面渲染页面。
至此,我们开启了自己的Node之旅?
URL模块是什么呢?
我们在控制台(终端)开启Node模式,并打印出url来看一下:
然后,发现它有Url、parse、resolve、resolveObject、format、URL、URLSearchParams、domainToASCII、domainToUnicode 这么多模块。
那么,这些模块都有什么用呢?
先看一下代码吧:
url.js
// 1. 引入 url 模块
var url = require("url");
// 2. 引入 http 模块
var http = require("http");
// 3. 用 http 模块创建服务
http.createServer(function (req, res) {
// 4. 获取服务器请求
if(req.url != "/favicon.ico") {
// 5. 使用 url 的 parse 方法
var result = url.parse(req.url, true);
console.log(result);
console.log(result.query.userName); // jsliang
console.log(result.query.userAge); // 23
}
// 设置 HTTP 头部,状态码是 200,文件类型是 html,字符集是 utf8
res.writeHead(200, {
"Content-Type": "text/html;charset=UTF-8"
});
// 往页面打印值
res.write('Hello NodeJS');
// 结束响应
res.end();
}).listen(3000);
在上面的代码中:
首先,引入该章节的主角url模块:
// 1.引入url模块
var url = require("url");
然后,引入http模块:
// 2.引入http模块
var http = require("http");
接着,创建http模块,因为url的监听,需要http模块的开启:
// 3.用http模块创建服务
http.createServer(function (req, res) {
// ...第4步、第5步代码
// 设置HTTP头部,状态码是200,文件类型是html,字符集是utf8
res.writeHead(200, {
"Content-Type": "text/html;charset=UTF-8"
});
//往页面打印值
res.write('Hello World');
//结束响应
res.end();
}).listen(3000);
最后,访问给出的地址: http://localhost:3000/?userName=jsliang&userAge=23 ,并通过它查看url的parse模块怎么用,输出啥:
// 4.获取服务器请求
?userName=jsliang&userAge=23
*/ /favicon.ico
*这里为了防止重复执行,所以排除req.url == /favicon.ico的情况
*/
if(req.url != "/favicon.ico") {
//5. 使用url的parse方法
var result = url.parse(req.url, true);
console.log(result);
- 内容2:parse如何使用
console.log(url.parse("http://www.baidu.com"));
- 内容3:parse带参数
console.log(url.parse("http://www.baidu.com/new?name=zhangsan"));
- 内容4:format的使用
console.log(url.format({
protocol: 'http:',
slashes: true,
auth: null,
host: 'www.baidu.com',
port: null,
hostname: 'www.baidu.com',
hash: null,
search: '?name=zhangsan',
query: 'name=zhangsan',
pathname: '/new',
path: '/new?name=zhangsan',
href: 'http://www.baidu.com/new?name=zhangsan'
}))
// Console:
// http://www.baidu.com/new?name=zhangsan
- 内容5:resolve的使用
console.log(url.resolve("http://www.baidu.com/jsliang", "梁峻荣"));
// Console:
// http://www.baidu.com/梁峻荣
更多关于url的相关配置,可以查看官网。
1.3 CommonJS-
什么是CommonJS ?
CommonJS就是为了JS 的表现来制定规范,因为JS没有模块系统、标准库较少、缺乏包管理工具,所以CommonJS应运而生,它希望JS可以在任何地方运行,而不是只在游览器中,从而达到Java、C#、PHP这些后端语言具备开发大型应用的能力。 -
CommonJS 的应用?
1.服务器端JavaScript应用程序;(Node.js)
2.命令行工具;
3.桌面图形界面应用程序。 -
CommonJS与Node.js的关系?
CommonJS就是模块化的标准,Node.js就是CommonJS(模块化)的实现。 -
Node.js 中的模块化 ?
1.在Node中,模块分为两类:一是Node提供的模块,称为核心模块;二是用户编写的模块,成为文件模块。核心模块在Node源代码的编译过程中,编译进行了二进制执行文件,所以它的加载速度是最快的,例如:HTTP模块、URL模块、FS模块;文件模块是在运行时动态加载的,需要完整的路径进行分析、文件定位、编译执行过程等…所以它的速度相对核心模块来说会更慢一些。
2.我们可以将公共的功能抽离出一个单独的JS文件存放,然后,在需要的情况下,通过export或者module.exports将模块导出,并通过require引入这些模块。
现在,通过三种使用方式,来讲解下Node中的模块化及exports/require的使用。
先查看下目录:
-
方法一:
首先,新建03_CommonJS.js、03_tool-add.js、node_modules/03_tool-multiply.js 、node_modules/jsliang-module/tools.js 这4个文件/文件夹。
其中,package.json 暂且不理会,稍后会讲解它如何自动生成。
在03_tool-add.js 中:
03_tool-add.js
// 1. 假设我们文件其中有个工具模块
var tools = {
add: (...numbers) => {
let sum = 0;
for (let number in numbers) {
sum += numbers[number];
}
return sum;
}
}
// exports 使用方法
// var str = "jsliang is very good!";
// exports.str = str; // { str: 'jsliang is very good!' }
// module.exports 使用方法
module.exports = tools;
那么,上面的代码有啥定义呢?
第一步,定义了个工具库tools;
第二步,通过modules.exports 将tools 进行了导出;
所以,在03_CommonJS.js 可以通过require 导入使用:
var http = require("http");
var tools1 = require('./03_tool-add');
http.createServer(function (req, res) {
res.writeHead(200, {
"Content-Type": "text/html;charset=UTF-8"
});
res.write('Hello NodeJS');
console.log(tools1.add(1, 2, 3));
res.end();
}).listen(3000);
这样,就完成了exports与require的初次使用。
- 方法二:
当我们模块文件过多的时候,应该需要有个存放这些模块的目录,Node就很靠谱,它规范我们可以将这些文件都房子node_modules目录中。
所以,在node_modules中新建一个03_tool-multiply.js 文件,其内容如下:
03_tool-multiply.js
var tools = {
multiply: (...numbers) => {
let sum = numbers[0];
for (let number in numbers) {
sum = sunm * numbers[numbers];
}
return sum;
}
}
module.exports = tools;
在引用方面,只需要通过:
// 如果Node在当前目录没找到too.js 文件,则会去node_modules 里面去查找
var tools2 = require('03_tool-multiply');
console.log(tools2.multiply(1,2,3,4));
这样,就可以成功导入03_tool-multiply.js 文件了。
- 方法三:
如果,全部单个文件丢在node_modules 上,它会显得杂乱无章,所以应该定义个自己的模块:jsliang-module ,然后将我们的tool.js 存放在该目录中:
jsliang-module/tools.js
var tools = {
add: (...numbers) => {
let sum = 0;
for (let number in numbers) {
sum += numbers[number];
}
return sum;
},
multiply: (...numbers) => {
let sum = numbers[0];
for (let number in numbers) {
sum = sum * numbers[number];
}
return sum;
}
}
module.exports = tools;
这样,就定义好了自己的工具库。
但是,如果我们通过var tools3 = require(‘jsliang-module’); 去导入,会发现它报error了,所以,我们应该在jsliang-module 目录下,通过下面命令行生成一个package.json 。
PS E:MyWebnode_modulesjsliang-nodule>npm init --yes
这样,在jsliang-module 中就有了package.json ;
而我们在03_CommonJS.js 就可以引入它了:
03_CommonJS.js
var http = require("http");
var tools1 = require('./03_tool-add');
// 如果 Node 在当前目录没找到 tool.js 文件,则会去 node_modules 里面去查找
var tools2 = require('03_tool-multiply');
var tools3 = require('jsliang-module');
http.createServer(function (req, res) {
res.writeHead(200, {
"Content-Type": "text/html;charset=UTF-8"
});
res.write('Hello NodeJS');
console.log(tools1.add(1, 2, 3));
console.log(tools2.multiply(1, 2, 3, 4));
console.log(tools3.add(4, 5, 6));
res.end();
}).listen(3000);
至此,我们就通过三种方法,了解了各种exports 和 require 的姿势以及Node 模块化的概念了。
1.4 fs文件管理下面,我们讲解下fs文件管理:
如需快速找到下面某个内容,请使用Ctrl + F
- fs.stat 检测是文件还是目录 ;
- fs.mkdir 创建目录 ;
- fs.writeFile 创建写入文件 ;
- fs.appendFile 追加文件
- fs.readFile 读取文件 ;
- fs.readdir 读取目录 ;
- fs.rename 重命名 ;
- fs.rmdir 删除目录 ;
- fs.unlink 删除文件 。
此章节的文件目录:
首先,通过fs.stat 检查一个读取的是文件还是目录:
05_fs.js
// 2. fs.mkdir
let fs = require('fs');
fs.mkdir('css', (err) => {
if(err) {
console.log(err);
return false;
} else {
console.log("创建目录成功");
// Console: 创建目录成功!
}
})
通过node 05_fs.js ,发现目录中多了一个 css 文件夹 。
那么,有创建就有删除,创建的目录如何删除呢?这里讲解下 fs.rmdir :
05_fs.js
// 8. fs.rmdir
let fs = require('fs');
fs.rmdir('css',(err) => {
if(err) {
console.log(err);
return false;
} else {
console.log("创建目录成功");
// Console: 创建目录成功!
}
})
通过 node 05_fs.js ,我们发现目录中的 css 文件夹被删除了。
接着,通过 fs.writeFile 来创建写入文件 :
05_fs.js
// 3. fs.writeFile
let fs = require('fs');
fs.writeFile('index.js','Hello jsliang', (err) => {
if(err) {
console.log(err);
return false;
} else {
console.log('写入成功');
}
})
知得注意的是,这样的写入,是清空原文件中的所有数据,然后添加Hello jsliang这句话。即:存在即覆盖,不存在即创建。
有创建就有删除,感兴趣的可以使用 fs.unlink 进行文件的删除,再次不做过多讲解。
既然,上面的是覆盖文件,那么有没有追加文件呢?有的,使用 fs.appendFile 吧 :
05_fs.js
// 4. fs.appendFile
let fs = require('fs');
fs.appendFile('index.js','这段文本是要追加的内容',(err) => {
if(err) {
console.log(err);
return false;
} else {
console.log("追加成功");
}
})
这样,就成功往里面追加了一段话,从而使 index.js 变成了:
index.js
Hello jsliang这段文本是要追加的内容
在上面,我们已经做了:新增、修改、删除操作。那么,小伙伴一定很熟悉下一步是做什么了:
- fs.readFile 读取文件;
- fs.readdir 读取目录
let fs = require('fs');
// 5. fs.readFile
fs.readFile('index.js', (err, data) => {
if(err) {
console.log(err);
return false;
} else {
console.log("读取文件成功!");
console.log(data);
// Console:
// 读取文件成功!
//
}
})
// 6. fs.readdir 读取目录
fs.readdir('node_modules', (err, data) => {
if(err) {
console.log(err);
return false;
} else {
console.log("读取目录成功!");
console.log(data);
// Console:
// 读取目录成功!
// [ '03_tool-multiply.js', 'jsliang-module' ]
}
})
如上,成功做到了读取文件和读取目录。
最后,再回顾一开始的目标:
1.fs.stat 检测是文件还是目录
2.fs.mkdir 创建目录
3.fs.writeFile 创建写入文件
4.fs.appendFile 追加文件
5.fs.readFile 读取文件
6.fs.readdir 读取目录
7.fs.rename 重命名
8.fs.rmdir 删除目录
9.fs.unlink 删除文件
很好,就剩下重命名了:
05_fs.js
let fs = require('fs');
// 7. fs.rename 重命名
fs.rename('index.js', 'jsliang.js', (err) => {
if(err) {
console.log(err);
return false;
} else {
console.log("重命名成功!");
}
})
当然,如果 fs.rename 还有更厉害的功能:剪切
05_fs.js
let fs = require('fs');
// 7. fs.rename 重命名
fs.rename('jsliang.js', 'node_modules/jsliang.js', (err) => {
if(err) {
console.log(err);
return false;
} else {
console.log("剪切成功!");
}
})
通通搞定,现在目录变成了:
在上一章节中,我们了解了 fs 的文件管理。
那么,在这里,我们尝试使用 fs 做点小事情:
06_fsDemo.js
// 1. 判断服务器上面有没有 upload 目录,没有就创建这个目录
// 2. 找出 html 目录下面的所有的目录,然后打印出来
let fs = require('fs');
// 图片上传
fs.stat('upload', (err, stats) => {
// 判断有没有 upload 目录
if(err) {
// 如果没有
fs.mkdir('upload', (error) => {
if(error) {
console.log(error);
return false;
} else {
console.log("创建 upload 目录成功!");
}
})
} else {
// 如果有
console.log(stats.isDirectory());
console.log("有 upload 目录,你可以做更多操作!");
}
})
// 读取目录全部文件
fs.readdir('node_modules', (err, files) => {
if(err) {
console.log(err);
return false;
} else {
// 判断是目录还是文件夹
console.log(files);
let filesArr = [];
(function getFile(i) {
// 循环结束
if(i == files.length) {
// 打印出所有目录
console.log("目录:");
console.log(filesArr);
return false;
}
// 判断目录是文件还是文件夹
fs.stat('node_modules/' + files[i], (error, stats) => {
if(stats.isDirectory()) {
filesArr.push(files[i]);
}
// 递归调用
getFile(i+1);
})
})(0)
}
})
1.6 fs流
我们了解下 fs 流及其读取:
// 新建 fs
const fs = require('fs');
// 流的方式读取文件
let fileReadStream = fs.createReadStream('index.js');
// 读取次数
let count = 0;
// 保存数据
let str = '';
// 开始读取
fileReadStream.on('data', (chunk) => {
console.log(`${++count} 接收到:${chunk.length}`);
// Console:1 接收到:30
str += chunk;
})
// 读取完成
fileReadStream.on('end', () => {
console.log("——结束——");
console.log(count);
console.log(str);
// Console:——结束——
// 1
// console.log("Hello World!");
})
// 读取失败
fileReadStream.on('error', (error) => {
console.log(error);
})
在这里,通过 fs 模块的 createReadStream 创建了读取流,然后,读取文件 index.js ,从而最后在控制台输出了:
1 接收到:259
——结束——
1
console.log("尽信书,不如无书;尽看代码,不如删掉这些文件。");
console.log("尽信书,不如无书;尽看代码,不如删掉这些文件。");
console.log("尽信书,不如无书;尽看代码,不如删掉这些文件。");
其中,console.log() 那三行就是index.js的文本内容 。
然后,试下流的存入:
let fs = require('fs');
let data = 'console.log("Hello World! 我要存入数据!")';
// 创建一个可以写入的流,写入到文件 index.js 中
let writeStream = fs.createWriteStream('index.js');
// 开始写入
writeStream.write(data, 'utf8');
// 写入完成
writeStream.end();
writeStream.on('finish', () => {
console.log('写入完成!');
// Console:写入完成
});
我们打开 index.js,会发现里面的内容变成了 console.log("Hello World! 我要存入数据!"),依次,我们通过流的形式进行了读取和写入的操作。
1.7 创建web服务器在这里,我们利用http模块、url模块、path模块、fs模块创建一个web服务器。
什么是web服务器?
web服务器一般指网站服务器,是指驻留在因特网上某种类型计算机的程序,可以像游览器等web客户端提供文档,也可以放置网站文件,让全世界游览;可以放置数据文件,让全世界下载。目前,最主流的三个web服务器是Apache 、Nginx 、IIS 。
下面,我们使用Node来创建一个Web服务:
08_WebService.js
// 引入 http 模块
let http = require("http");
// 引入 fs 模块
let fs = require("fs");
http.createServer((req, res) => {
// 获取响应路径
let pathName = req.url;
// 默认加载路径
if (pathName == "/") {
// 默认加载的首页
pathName = "index.html";
}
// 过滤 /favicon.ico 的请求
if (pathName != "/favicon.ico") {
// 获取 08_WebService 下的 index.html
fs.readFile("./08_WebService/" + pathName, (err, data) => {
if (err) {
// 如果不存在这个文件
console.log("404 Not Found!");
fs.readFile('./08_WebService/404.html', (errorNotFound, dataNotFound) => {
if(errorNotFound) {
console.log(errorNotFound);
} else {
res.writeHead(200, {
"Content-Type": "text/html; charset='utf-8'"
});
// 读取写入文件
res.write(dataNotFound);
// 结束响应
res.end();
}
})
return;
} else {
// 返回这个文件
// 设置请求头
res.writeHead(200, {
"Content-Type": "text/html; charset='utf-8'"
});
// 读取写入文件
res.write(data);
// 结束响应
res.end();
}
});
}
}).listen(8080);
这样,在游览器输入 localhost:8080 即可以看到 :
然后,你会发现它就加载了整个 index.html 文件,连CSS这些没引入么?
所以,下一步,要动态加载 html 、css 、以及 js :
08_WebService.js
// 引入 http 模块
let http = require("http");
// 引入 fs 模块
let fs = require("fs");
// 引入 url 模块
let url = require("url");
// 引入 path 模块
let path = require("path");
http.createServer((req, res) => {
// 获取响应路径
let pathName = url.parse(req.url).pathname;
// 默认加载路径
if (pathName == "/") {
// 默认加载的首页
pathName = "index.html";
}
// 获取文件的后缀名
let extName = path.extname(pathName);
// 过滤 /favicon.ico 的请求
if (pathName != "/favicon.ico") {
// 获取 08_WebService 下的 index.html
fs.readFile("./08_WebService/" + pathName, (err, data) => {
// 如果不存在这个文件
if (err) {
console.log("404 Not Found!");
fs.readFile(
"./08_WebService/404.html",
(errorNotFound, dataNotFound) => {
if (errorNotFound) {
console.log(errorNotFound);
} else {
res.writeHead(200, {
"Content-Type": "text/html; charset='utf-8'"
});
// 读取写入文件
res.write(dataNotFound);
// 结束响应
res.end();
}
}
);
return;
}
// 返回这个文件
else {
// 获取文件类型
let ext = getExt(extName);
// 设置请求头
res.writeHead(200, {
"Content-Type": ext + "; charset='utf-8'"
});
// 读取写入文件
res.write(data);
// 结束响应
res.end();
}
});
}
}).listen(8080);
// 获取后缀名
getExt = (extName) => {
switch(extName) {
case '.html': return 'text/html';
case '.css': return 'text/css';
case '.js': return 'text/js';
default: return 'text/html';
}
}
这样,当我们再次请求的时候,游览器就变成了:
当然,在上面,我们仅仅模拟了 html、css、js 这三种文件类型而已,我们需要模拟更多的文件类型:
然后,我们需要修改下我们的 js 文件,让它适应多种请求响应了:
08_WebService.js
// 引入 http 模块
let http = require("http");
// 引入 fs 模块
let fs = require("fs");
// 引入 url 模块
let url = require("url");
// 引入 path 模块
let path = require("path");
http.createServer((req, res) => {
// 获取响应路径
let pathName = url.parse(req.url).pathname;
// 默认加载路径
if (pathName == "/") {
// 默认加载的首页
pathName = "index.html";
}
// 获取文件的后缀名
let extName = path.extname(pathName);
// 过滤 /favicon.ico 的请求
if (pathName != "/favicon.ico") {
// 获取 08_WebService 下的 index.html
fs.readFile("./08_WebService/" + pathName, (err, data) => {
// 如果不存在这个文件
if (err) {
console.log("404 Not Found!");
fs.readFile(
"./08_WebService/404.html",
(errorNotFound, dataNotFound) => {
if (errorNotFound) {
console.log(errorNotFound);
} else {
res.writeHead(200, {
"Content-Type": "text/html; charset='utf-8'"
});
// 读取写入文件
res.write(dataNotFound);
// 结束响应
res.end();
}
}
);
return;
}
// 返回这个文件
else {
// 获取文件类型
let ext = getExt(extName);
console.log(ext);
// 设置请求头
res.writeHead(200, {
"Content-Type": ext + "; charset='utf-8'"
});
// 读取写入文件
res.write(data);
// 结束响应
res.end();
}
});
}
}).listen(8080);
// 获取后缀名
getExt = (extName) => {
// readFile 是异步操作,所以需要使用 readFileSync
let data = fs.readFileSync('./08_ext.json');
let ext = JSON.parse(data.toString());
return ext[extName];
}
如此,我们做了个简单的 Web 服务器。
1.8 非阻塞I/O事件驱动 Java、PHP或者.NET等服务端语言,会为每一个客户端的连接创建一个新的线程。
Node不会为每一个客户连接创建一个新的线程,而仅仅使用一个线程。
当有用户连接了,就会触发一个内部事件,通过非阻塞I/O、事件驱动机制,让Node程序宏观上也是并行的。
使用Node,一个8GB内存的服务器,可以同时处理超过4万用户的连接。
在这一章节中,主要解决:
1.Node 的非阻塞I/O是什么?
2.Node events 模块是什么?
首先,在我们正常编程中,我们是希望程序能够一行一行按照我们的意愿编写的:
09_io.js
console.log("1");
console.log("2");
console.log("3");
但是,事与愿违。
我们有时候,会执行一些异步方法(函数):
09_io.js
console.log("1");
// console.log("2");
let fs = require('fs');
getExt = () => {
fs.readFile('08_ext.json', (err, data) => {
console.log("2");
})
}
getExt();
console.log("3");
在上面代码中,由于 fs.readFile 是 Node 的异步函数。所以,程序先执行了 1 和 3,最后才执行 fs.readFile 的 2 部分。
在这里,可以看出 Node 不会因为一段代码的逻辑错误,从而导致其他代码无法运行。
这样子,就导致了一个问题:步骤3可能拿不到步骤2的执行结果了。这就是Node的非阻塞性I/O驱动。
那么,我们有没有办法解决这个问题呢?
有的!
1.通过回调函数;
2.通过 Node 的 events 模块。
09_io.js
let fs = require("fs");
getExt = (callback) => {
fs.readFile('08_ext.json', (err, data) => {
callback(data);
})
}
getExt( (result) => {
console.log(result.toString());
})
通过回调,我们可以将 getExt 的数据提取出来。
然后,我们通过 Node 的 events 模块来解决这个异步问题:
// 引入 fs 模块
let fs = require("fs");
// 引入 events 模块
let events = require("events");
// 实例化事件对象
let EventEmitter = new events.EventEmitter();
getExt = () => {
fs.readFile('08_ext.json', (err, data) => {
// 将 data 广播出去
EventEmitter.emit('data', data.toString());
})
};
getExt();
// 监听 data
EventEmitter.on('data', (ext) => {
console.log(ext);
});
在这里,EventEmitter.on 通过监听 data 的形式,获取了 getExt 内部的执行结果。
如此,我们就了解了 Node 的 I/O 事件及 events 模块
话不多说,先上代码:
index.js
// 加载 http 模块
var http = require('http');
// 虚拟 SQL 读取出来的数据
var items = [];
// 创建 http 服务
http.createServer(function (req, res) {
// 设置跨域的域名,* 代表允许任意域名跨域
res.setHeader('Access-Control-Allow-Origin', '*');
// 设置 header 类型
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
// 跨域允许的请求方式
res.setHeader('Content-Type', 'application/json');
// 判断请求
switch (req.method) {
// post 请求时,浏览器会先发一次 options 请求,如果请求通过,则继续发送正式的 post 请求
case 'OPTIONS':
res.statusCode = 200;
res.end();
break;
// 如果是 get 请求,则直接返回 items 数组
case 'GET':
let data = JSON.stringify(items);
res.write(data);
res.end();
break;
// 如果是 post 请求
case 'POST':
let item = '';
// 读取每次发送的数据
req.on('data', function (chunk) {
item += chunk;
});
// 数据发送完成
req.on('end', function () {
// 存入
item = JSON.parse(item);
items.push(item.item);
// 将数据返回到客户端
let data = JSON.stringify(items);
res.write(data);
res.end();
});
break;
}
}).listen(3000)
console.log('http server is start...');
首先,加载了http模块,并创建了服务;
然后,设置了跨域的处理方式,允许进行跨域;
接着,进行了请求的判断处理,由于只做了简单演练,故只判断是get请求还是post请求;
最后,将请求的结果返回给客户端。
在上面,我们进行了后端 Node 的部署,那么前端页面要怎么做呢?
index.html
Node Web Todo List
- {{ item }}
我们通过 Vue 进行了布局,通过 Axios 进行了接口的请求。从而完成了对数据的操作。
1.9 Node连接MySQL首先,我们通过可视化工具进行表的设计:
| 名 | 类型 | 长度 | 键 |
|---|---|---|---|
| id | int | 11 | 主键 |
| name | varchar | 255 | |
| age | varchar | 255 |
然后,我们进行表的填充:
| id | name | age |
|---|---|---|
| 1 | jsliang | 23 |
| 2 | liang | 25 |
接着,我们安装 Node 连接 MySQL 的包:
npm i mysql -D
再来,我们编写 Node 的 index.js:
index.js
var mysql = require('mysql');
var connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: '123456',
database: 'node'
});
connection.connect();
connection.query('SELECt * FROM user', function (error, results, fields) {
if (error) throw error;
console.log(results);
});
connection.end();
最后,我们通过 node index.js,打开该服务:
[ RowDataPacket { id: 1, name: 'jsliang', age: '23' },
RowDataPacket { id: 2, name: 'liang', age: '25' } ]
如此,我们便完成了 Node 连接 MySQL。
———————华丽分割线———————
当然,增删改查是后端的基本操作,所以在这里,我们可以补全基本的增删改查功能。
先看目录:
- 新增表字段
add.js
var mysql = require('mysql');
var connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: '123456',
database: 'node'
});
connection.connect();
let addSql = "INSERT INTO user(id,name,age) VALUES(0,?,?)";
let addSqlParams = ["jsliang", "23"];
connection.query(addSql, addSqlParams, function (err, res) {
if (err) {
console.log("新增错误:");
console.log(err);
return;
} else {
console.log("新增成功:");
console.log(res);
}
});
connection.end();
我们只需要直接 node add.js,就能往数据库中新增数据了。
- 删除表字段
delete.js
// 连接 MySQL
var mysql = require('mysql');
// MySQL 的连接信息
var connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: '123456',
database: 'node'
});
// 开始连接
connection.connect();
// 新增的 SQL 语句及新增的字段信息
var delSql = 'DELETE FROM user where id = 2';
// 连接 SQL 并实施语句
connection.query(delSql, function (err, res) {
if (err) {
console.log("删除错误:");
console.log(err);
return;
} else {
console.log("删除成功:");
console.log(res);
}
});
// 终止连接
connection.end();
- 修改表字段
update.js
// 连接 MySQL
var mysql = require('mysql');
// MySQL 的连接信息
var connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: '123456',
database: 'node'
});
// 开始连接
connection.connect();
// 新增的 SQL 语句及新增的字段信息
let updateSql = "UPDATE user SET name = ?,age = ? WHERe Id = ?";
let updateSqlParams = ["LiangJunrong", "23", 1];
// 连接 SQL 并实施语句
connection.query(updateSql, updateSqlParams, function (err, res) {
if (err) {
console.log("修改错误:");
console.log(err);
return;
} else {
console.log("修改成功:");
console.log(res);
}
});
// 终止连接
connection.end();
- 查询表字段
read.js
// 连接 MySQL
var mysql = require('mysql');
// MySQL 的连接信息
var connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: '123456',
database: 'node'
});
// 开始连接
connection.connect();
// 新增的 SQL 语句及新增的字段信息
let readSql = "SELECT * FROM user";
// 连接 SQL 并实施语句
connection.query(readSql, function (err, res) {
if (err) throw err;
console.log(res);
});
// 终止连接
connection.end();
以上,我们打通了 Node 与 MySQL 的壁垒,实现了数据的增删改查。
二、Web实战----企业官网 2.1 编程环境首先,我们查看下我们的前端基本代码:地址
然后,我们进行后端功能分析:
- 留言板。用户点击 留言板 的时候,需要先判断用户是否登录。如果用户尚未登录,则直接跳转到 登录页;如果用户登录了,则显示 留言板页面。
在 留言板页面 中,存在两个接口:
- 获取留言内容: 调取 getMessage 接口,返回全部留言信息,由于预计信息不多,故这里不做分页功能,有需要的小伙伴在实现完这个功能后,可以进行分页接口的设计;
- 提交留言内容:调取 sendMessage 接口,将用户名、用户 id、留言内容发送给后端。
- 在 登录页面 中,存在一个接口:
登录:调取 login 接口,提交用户填写的姓名和密码。 - 在 注册页面 中,存在一个接口:
注册:调取 register 接口,提交用户填写的姓名和密码。
由此,我们可以设计下前后端的接口结合:
接口文档
| 接口 | 类型 | 参数 | 返回信息 |
|---|---|---|---|
| getMessage:获取留言信息 | get | 无参 | n 条记录:id(用户 id)、user_name(用户名)、user_message(用户留言内容)、time(留言时间) |
| sendMessage:提交留言信息 | post | id(用户 id)、user_name(用户名)、user_message(用户留言内容) | status 状态 |
| login:登录 | post | id(用户 id)、user_name(用户名)、user_password(用户密码) | status 状态 |
| register:注册 | post | id(用户 id)、user_name(用户名)、user_password(用户密码) | status 状态 |
最后,我们进行 MySQL 数据库的表设计:
user 表
| 名 | 类型 | 长度 | 键 |
|---|---|---|---|
| id | int | 11 | 主键 |
| user_name | varchar | 255 | |
| user_password | varchar | 255 | |
| time | datetime |
message 表
| 名 | 类型 | 长度 | 键 |
|---|---|---|---|
| id | int | 11 | 主键 |
| user_name | varchar | 255 | |
| user_password | varchar | 255 | |
| time | datetime |
在我们进行实操之前,先确认我们是否能写接口,所以我们可以新建一个 test 文件夹,里面放一个 index.html 以及一个 index.js 来测试一下。
- text - index.html - index.js
首先,我们就 2.1 提到的接口,提前进行后端接口的设置:
index.js
// 连接 MySQL:先安装 npm i mysql -D
var mysql = require('mysql');
// MySQL 的连接信息
var connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: '123456',
database: 'nodebase'
});
// 开始连接
connection.connect();
// 引入 http 模块:http 是提供 Web 服务的基础
const http = require("http");
// 引入 url 模块:url 是对用户提交的路径进行解析
const url = require("url");
// 引入 qs 模块:qs 是对路径进行 json 化或者将 json 转换为 string 路径
const qs = require("querystring");
// 用 http 模块创建服务
http.createServer(function (req, res) {
// 设置 cors 跨域
res.setHeader("Access-Control-Allow-Origin", "*");
// 设置 header 类型
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
// 跨域允许的请求方式
res.setHeader('Content-Type', 'application/json');
if (req.method == "POST") { // 接口 POST 形式
console.log("n【POST 形式】");
// 获取前端发来的路由地址
let pathName = req.url;
console.log("n接口为:" + pathName);
// 接收发送过来的参数
let tempResult = "";
// 数据接入中
req.addListener("data", function (chunk) {
tempResult += chunk;
});
// 数据接收完成
req.addListener("end", function () {
var result = JSON.stringify(qs.parse(tempResult));
console.log("n参数为:");
console.log(result);
if (pathName == "/sendMessage") { // 提交留言信息
console.log("n【API - 提交留言信息】");
} else if (pathName == "/login") { // 登录
console.log("n【API - 登录】");
} else if (pathName == "/register") { // 注册
console.log("n【API - 注册】");
}
// 接口信息处理完毕
})
// 数据接收完毕
} else if (req.method == "GET") { // 接口 GET 形式
console.log("n【GET 形式】");
// 解析 url 接口
let pathName = url.parse(req.url).pathname;
console.log("n接口为:" + pathName);
if (pathName == "/getMessage") { // 获取留言信息
console.log("n【API - 获取留言信息】");
} else if(pathName == "/") { // 首页
res.writeHead(200, {
"Content-Type": "text/html;charset=UTF-8"
});
res.write('jsliang 前端有限公司服务已开启!详情可见:Node 基础');
res.end();
}
}
}).listen(8888); // 监听的端口
// 获取当前时间
function getNowFormatDate() {
var date = new Date();
var year = date.getFullYear(); // 年
var month = date.getMonth() + 1; // 月
var strDate = date.getDate(); // 日
var hour = date.getHours(); // 时
var minute = date.getMinutes(); // 分
var second = date.getMinutes(); // 秒
if (month >= 1 && month <= 9) {
month = "0" + month;
}
if (strDate >= 0 && strDate <= 9) {
strDate = "0" + strDate;
}
// 返回 yyyy-mm-dd hh:mm:ss 形式
var currentdate = year + "-" + month + "-" + strDate + " " + hour + ":" + minute + ":" + second;
return currentdate;
}
通过判断 req.method 属于 GET 还是 POST 形式,从而确定加载的接口:
- 在 POST 中,判断是属于 提交留言信息、登录 还是 注册;
- 在 GET 中,判断是不是 获取留言信息。
同时,我们在其中定义了 MySQL 的连接以及一个 getNowFormatDate 用来获取当前时间,格式为:2018-12-21 10:03:59
然后,我们通过一个前端页面来演示我们的接口是否能使用:
index.html
演示代码
最后,我们通过 node index.js,并打开 index.html,通过 F12 控制台查看我们的接口是否正常:
可以看到我们的接口能正常调通,这样我们就可以连接数据库,进行这 4 个接口的设计了。
2.3 注册功能如果小伙伴们觉得每次更新 Node 代码后,又要重启一遍 node index.js 觉得麻烦,可以通过 supervisor 来监听 Node 代码的改动
很好,我们回到仿企业网站的页面上,准备编写接口以及丰富 Node 的接口。
首先,我们开启前端和 Node 服务:
- 打开命令行/终端 ;
- 开启前端
cd FrontEndCode
live-server
安装 live-server:npm i live-server -g
- 开启后端
cd NodeWeb
supervisor index.js
安装 supervisor:npm i supervisor -g
然后,我们在注册页面通过点击事件来触发调接口:
register.html
注册-jsliang 前端有限公司
如此,我们在用户点击 注册 按钮的时候,进行接口的调用,发送数据到了后端,如果成功了,那就弹窗,并跳转到登录页;如果没成功,就弹窗提示。
接着,我们编写 Node,前端调用接口后,Node 判断这两个参数是否为空,如果不为空,则将数据存储到数据库。
index.js
// ... 其他代码省略,请自行前往章节 2.2 后端接口 获取其他代码
if (pathName == "/sendMessage") { // 提交留言信息
console.log("n【API - 提交留言信息】");
} else if (pathName == "/login") { // 登录
console.log("n【API - 登录】");
} else if (pathName == "/register") { // 注册
console.log("n【API - 注册】");
result = JSON.parse(result);
let username = result.username; // 用户名
let password = result.password; // 密码
let time = getNowFormatDate(); // 时间
if (!username) { // 用户名为空
res.end("注册失败,用户名为空。");
return;
} else if (!password) { // 密码为空
res.end("注册失败,密码为空!");
return;
} else if(username.length > 10) { // 姓名过长
res.end("注册失败,姓名过长!");
return;
} else if(password.length > 20) { // 密码过长
res.end("注册失败,密码过长!");
return;
} else {
// 查询 user 表
// 使用 Promise 的原因是因为中间调用了两次数据库,而数据库查询是异步的,所以需要用 Promise。
new Promise( (resolve, reject) => {
// 新增的 SQL 语句及新增的字段信息
let readSql = "SELECt * FROM user";
// 连接 SQL 并实施语句
connection.query(readSql, function (error1, response1) {
if (error1) { // 如果 SQL 语句错误
throw error1;
} else {
console.log("nSQL 查询结果:");
// 将结果先去掉 RowDataPacket,再转换为 json 对象
let newRes = JSON.parse(JSON.stringify(response1));
console.log(newRes);
// 判断姓名重复与否
let userNameRepeat = false;
for(let item in newRes) {
if(newRes[item].user_name == username) {
userNameRepeat = true;
}
}
// 如果姓名重复
if(userNameRepeat) {
res.end("注册失败,姓名重复!");
return;
} else if(newRes.length > 300) { // 如果注册名额已满
res.end("注册失败,名额已满!");
return;
} else { // 可以注册
resolve();
}
}
});
}).then( () => {
console.log("n第二步:");
// 新增的 SQL 语句及新增的字段信息
let addSql = "INSERT INTO user(user_name,user_password, time) VALUES(?,?,?)";
let addSqlParams = [result.username, result.password, time];
// 连接 SQL 并实施语句
connection.query(addSql, addSqlParams, function (error2, response2) {
if (error2) { // 如果 SQL 语句错误
console.log("新增错误:");
console.log(error2);
return;
} else {
console.log("nSQL 查询结果:");
console.log(response2);
console.log("n注册成功!");
// 返回数据
res.write(JSON.stringify({
code: "0",
message: "注册成功!"
}));
// 结束响应
res.end();
}
});
})
// Promise 结束
}
// 注册流程结束
}
最后,我们在查看下该功能是否成功:
2.4 注册功能在上面,我们完成了注册功能,那么相对来说,登录功能就容易通了,因为查询部分我们已经试过了一次。
login.html
登录-jsliang 前端有限公司
编写完前端的代码后,我们进行 Node 代码的编辑:
index.js
// ... 其他代码省略,请自行前往章节 2.2 后端接口 获取其他代码
if (pathName == "/sendMessage") { // 提交留言信息
console.log("n【API - 提交留言信息】");
} else if (pathName == "/login") { // 登录
console.log("n【API - 登录】");
result = JSON.parse(result);
let username = result.username; // 用户名
let password = result.password; // 密码
if (!username) { // 用户名为空
res.end("登录失败,用户名为空!");
return;
} else if (!password) { // 密码为空
res.end("登录失败,密码为空!");
return;
} else if(username.length > 10) {
res.end("登录失败,姓名过长!");
return;
} else if(password.length > 20) {
res.end("登录失败,密码过长!");
return;
} else {
// 新增的 SQL 语句及新增的字段信息
let readSql = "SELECt * FROM user WHERe user_name = '" + username + "'";
// 连接 SQL 并实施语句
connection.query(readSql, function (error1, response1) {
if (error1) {
throw error1;
} else {
if(response1 == undefined || response1.length == 0) { // 不存在用户
res.end("n不存在该用户!");
return;
} else { // 存在用户
console.log("n存在该用户!");
let newRes = JSON.parse(JSON.stringify(response1));
console.log(newRes);
if(newRes[0].user_password == password) { // 密码正确
// 返回数据
res.write(JSON.stringify({
code: "0",
message: "登录成功!",
data: {
id: newRes[0].id,
userName: newRes[0].user_name
}
}));
res.end();
} else { // 密码错误
// 返回数据
res.write(JSON.stringify({
code: "1",
message: "登录失败,密码错误!"
}));
res.end();
}
// 判断密码正确与否完毕
}
// 存在用户处理结束
}
});
}
// 登录步骤结束
} else if (pathName == "/register") { // 注册
console.log("n【API - 注册】");
}
很好,前端和后端都编写完毕,是时候查验下功能是否实现了:
2.5 留言功能现在,我们就剩下留言功能了,一鼓作气做好它吧!
messageBoard.html
留言板-jsliang 前端有限公司
接着编写下 Node 后端:
index.js
// ... 其他代码省略,请自行前往章节 2.2 后端接口 获取其他代码
if (pathName == "/sendMessage") { // 提交留言信息
console.log("n【API - 提交留言信息】");
result = JSON.parse(result);
let id = result.userid; // id
let userName = result.username; // 用户名
let messageText = result.message; // 留言内容
let time = getNowFormatDate(); // 时间
if(!messageText) {
res.end("登录失败,留言内容为空!");
return;
} else if(messageText.length > 140) {
res.end("登录失败,字数超过限制!");
return;
} else {
// 新增的 SQL 语句及新增的字段信息
let addSql = "INSERT INTO message(user_message, user_id, user_name, time) VALUES(?, ?, ?, ?)";
let addSqlParams = [messageText, id, userName, time];
// 连接 SQL 并实施语句
connection.query(addSql, addSqlParams, function (error1, response1) {
if (error1) { // 如果 SQL 语句错误
throw error1;
} else {
console.log("n新增成功!");
// 返回数据
res.write(JSON.stringify({
code: "0",
message: "新增成功!"
}));
// 结束响应
res.end();
}
})
}
} else if (pathName == "/login") { // 登录
console.log("n【API - 登录】");
} else if (pathName == "/register") { // 注册
console.log("n【API - 注册】");
}
// ... 其他代码省略,请自行前往章节 2.2 后端接口 获取其他代码
if (pathName == "/getMessage") { // 获取留言信息
console.log("n【API - 获取留言信息】");
// 解析 url 参数部分
let params = url.parse(req.url, true).query;
console.log("n参数为:");
console.log(params);
// 新增的 SQL 语句及新增的字段信息
let readSql = "SELECt * FROM message";
// 连接 SQL 并实施语句
connection.query(readSql, function (error1, response1) {
if (error1) {
throw error1;
} else {
let newRes = JSON.parse(JSON.stringify(response1));
console.log(newRes);
// 返回数据
res.write(JSON.stringify({
code: "1",
message: "查询成功!",
data: newRes
}));
// 结束响应
res.end();
}
});
// 查询完毕
} else if(pathName == "/") { // 首页
res.writeHead(200, {
"Content-Type": "text/html;charset=UTF-8"
});
res.write('jsliang 前端有限公司服务已开启!详情可见:Node 基础');
res.end();
}
敲完代码再看下功能是否实现:
综上,我们完成了所有的功能模块:注册、登录以及留言。
三、工具整合 3.1 supervisor – 监听Node改动正如其官网所说,它是一个进行控制系统:
1.安装插件:npm i supervisor -g
2.运行文件:supervisor app.js
3.查看运行:localhost:3000
平时,我们 node app.js 后,当我们修改了 app.js 的内容,就需要关闭 node 命令行再执行 node app.js。
而我们使用 supervisor 后,我们修改了 app.js 中的内容,只要点击保存,即可生效保存后的代码,实现实时监听 node 代码的变动。
PM2是Node进程管理工具,可以利用它来简化很多Node应用管理的繁琐任务,如性能监控、自动重启、负载均衡等,而且使用非常简单。
下面就对 PM2 进行入门性的介绍,基本涵盖了 PM2 的常用的功能和配置:
- 全局安装PM2 : npm i pm2 -g
- 监听应用 : pm2 start index.js
- 查看所有进程:pm2 list
- 查看某个进程: pm2 describe App name/id
- 停止某个进程: pm2 stop App name/id 。例如:
先通过 pm2 list 查看:
| App name | id | status |
|---|---|---|
| index | 0 | online |
只需要执行 pm2 stop index 或者 pm2 stop 0 即可。
- 停止所有进程: pm2 stop all
- 重启某个进程 : pm2 restart App name/id
- 删除某个进程 : pm2 delete App name/id
如上,如果说我们的 supervisor 是监听单个进程的话,那么 PM2 就是监听多个进程。
参考资料Node - 从0基础到实战企业官网 https://juejin.cn/post/6844903745755545614#heading-12



