前言:
本文是学习Node中间层实践的相关笔记,推荐大家可以去阅读jsliang大佬的原文Node-从0基础到实战企业官网。开启本文前,可以先去了解一下node的基本知识,会对了解本文内容有所帮助。当然,也可以选择,跟着本文,边读边了解。(PS:继续还债的node小白╮(╯▽╰)╭)
本文重点内容:
http.js的相关代码:
// 1. 引入 http 模块
var http = require("http");
// 2. 用 http 模块创建服务
/**
* req 获取 url 信息 (request)
* res 浏览器返回响应信息 (response)
*/
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模式。一般,像PHP这类老牌子的后端语言,需要Apache或者Nginx开启HTTP服务。然而,我们的Node不需要:
var http = require("http");
然后,开启HTTP服务,并设置开启的端口:
/**
* req 获取 url 信息 (request)
* res 浏览器返回响应信息 (response)
*/
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 模块创建服务
/**
* req 获取 url 信息 (request)
* res 浏览器返回响应信息 (response)
*/
http.createServer(function (req, res) {
// 4. 获取服务器请求
/**
* 访问地址是:http://localhost:3000/?userName=jsliang&userAge=23
* 如果你执行 console.log(req.url),它将执行两次,分别返回下面的信息:
* / ?userName=jsliang&userAge=23
* / /favicon.ico
* 这里为了防止重复执行,所以排除 req.url == /favicon.ico 的情况
*/
if(req.url != "/favicon.ico") {
// 5. 使用 url 的 parse 方法
/**
* parse 方法需要两个参数:
* 第一个参数是地址
* 第二个参数是 true 的话表示把 get 传值转换成对象
*/
var result = url.parse(req.url, true);
console.log(result);
/**
* Url {
* protocol: null,
* slashes: null,
* auth: null,
* host: null,
* port: null,
* hostname: null,
* hash: null,
* search: '?userName=jsliang&userAge=23',
* query: { userName: 'jsliang', userAge: '23' },
* pathname: '/',
* path: '/?userName=jsliang&userAge=23',
* href: '/?userName=jsliang&userAge=23' }
*/
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模块创建服务
/**
* req获取url信息 (request)
* res 游览器返回响应信息 (response)
*/
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.获取服务器请求
/**
*访问地址是:http://localhost:3000/?userName=jsliang&userAge=23
*如果执行console.log(req.url),它将执行两次,分别返回下面的信息:
*/ ?userName=jsliang&userAge=23
*/ /favicon.ico
*这里为了防止重复执行,所以排除req.url == /favicon.ico的情况
*/
if(req.url != "/favicon.ico") {
//5. 使用url的parse方法
/**
*parse方法需要两个参数:
*第一个参数是地址
*第二个参数是true的话表示把get传值转换给对象
*/
var result = url.parse(req.url, true);
console.log(result);
/**
*Url {
* protocol: null,
* slashes: null,
* auth: null,
* host: null,
* port: null,
* hostname: null,
* hash: null,
* search: '?userName=jsliang&userAge=23',
* query: { userName: 'jsliang',userAge: '23'},
* pathname: '/',
* path: '/?userName=jsliang&userAge=23',
* href: '/?userName=jsliang&userAge=23'}
* /
console.log(result.query.userName); //jsliang
console.log(result.query.userAge); // 23
}
从中,我们可以看出,可以通过query
,获取到我们想要的路径字段。
当然,上面只讲解了parse
的用法,我们可以将上面代码中的if语句里面的代码全部清空。然后,输入下面的内容,去学习url
模块更多的内容:
console.log(url);
/**
* Console:
{
Url: [Function: Url],
parse: [Function: urlParse], // 获取地址信息
resolve: [Function: urlResolve], // 追加或者替换地址
resolveObject: [Function: urlResolveObject],
format: [Function: urlFormat], // 逆向 parse,根据地址信息获取原 url 信息
URL: [Function: URL],
URLSearchParams: [Function: URLSearchParams],
domainToASCII: [Function: domainToASCII],
domainToUnicode: [Function: domainToUnicode]
}
*/
console.log(url.parse("http://www.baidu.com"));
/**
* Console:
Url {
protocol: 'http:',
slashes: true,
auth: null,
host: 'www.baidu.com',
port: null,
hostname: 'www.baidu.com',
hash: null,
search: null,
query: null,
pathname: '/',
path: '/',
href: 'http://www.baidu.com/'
}
*/
console.log(url.parse("http://www.baidu.com/new?name=zhangsan"));
/**
* Console:
Url {
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'
}
*/
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
console.log(url.resolve("http://www.baidu.com/jsliang", "梁峻荣"));
// Console:
// http://www.baidu.com/梁峻荣
更多关于url的相关配置,可以查看官网。
什么是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;
}
}
/**
* 2. 暴露模块
* exports.str = str;
* module.exports = str;
* 区别:
* module.exports 是真正的接口
* exports 是一个辅助工具
* 如果 module.exports 为空,那么所有的 exports 收集到的属性和方法,都赋值给了 module.exports
* 如果 module.exports 具有任何属性和方法,则 exports 会被忽略
*/
// 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));
/**
* Console:
* 6
* 6
* 这里要记得 Node 运行过程中,它请求了两次,
* http://localhost:3000/ 为一次,
* http://localhost:3000/favicon.ico 为第二次
*/
res.end();
}).listen(3000);
这样,就完成了exports
与require
的初次使用。
node_modules
目录中。node_modules
中新建一个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
存放在该目录中: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:\MyWeb\node_modules\jsliang-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');
/**
* 通过 package.json 来引用文件
* 1. 通过在 jsliang-module 中 npm init --yes 来生成 package.json 文件
* 2. package.json 文件中告诉了程序入口文件为 :"main": "tools.js",
* 3. Node 通过 require 查找 jsliang-module,发现它有个 package.json
* 4. Node 执行 tools.js 文件
*/
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));
/**
* Console:
* 6
* 24
* 15
* 6
* 24
* 15
* 这里要记得 Node 运行过程中,它请求了两次,
* http://localhost:3000/ 为一次,
* http://localhost:3000/favicon.ico 为第二次
*/
res.end();
}).listen(3000);
至此,我们就通过三种方法,了解了各种exports
和 require
的姿势以及Node 模块化的概念了。
下面,我们讲解下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');
/**
* 接收参数
* path - 将创建的目录路径
* mode - 目录权限(读写权限),默认 0777
* callback - 回调,传递异常参数 err
*/
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');
/**
* 接收参数
* path - 将创建的目录路径
* mode - 目录权限(读写权限),默认 0777
* callback - 回调,传递异常参数 err
*/
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');
/**
* filename (String) 文件名称
* data (String | Buffer) 将要写入的内容,可以是字符串或者 buffer 数据。
* · encoding (String) 可选。默认 'utf-8',当 data 是 buffer 时,该值应该为 ignored。
* · mode (Number) 文件读写权限,默认 438。
* · flag (String) 默认值 'w'。
* callback { Function } 回调,传递一个异常参数 err。
*/
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. 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 删除文件
*/
// 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)
}
})
我们了解下 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! 我要存入数据!")
,依次,我们通过流的形式进行了读取和写入的操作。
在这里,我们利用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 服务器。
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");
/**
* Console:
* 1
* 2
* 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");
/**
* Console:
* 1
* 3
* 2
*/
在上面代码中,由于 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");
/**
* Node 事件循环:
* 1.Node 是单进程单线程应用程序,但是通过事件和回调支持并发,所以性能非常高;
* 2.Node 的每一个API都是异步的,并作为一个独立线程运行,使用异步函数调用,并处理并发;
* 3.Node 有多个内置的事件,我们可以通过引入events 模块,并通过实例化EventEmitter 类来绑定和监听事件。
*/
// 引入 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
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta http-equiv="X-⌃-Compatible" content="ie=edge">
<title>Node Webtitle>
head>
<body>
<div id="app">
<h1>Todo Listh1>
<ul>
<li v-for="(item, index) in items" :key="index">{{ item }}li>
ul>
<input type="text" v-model="item">
<button @click="postApi">添加button>
div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js">script>
<script src="https://unpkg.com/axios/dist/axios.min.js">script>
<script>
new Vue({
el: document.getElementById('app'),
data: function () {
return {
items: [],
item: '',
}
},
created() {
// 进入页面请求数据
axios.get('http://localhost:3000/').then(res => {
console.log("\n【API - get 数据】");
console.log(res);
this.items = res.data;
}).catch(function (err) {
console.log(err)
})
},
methods: {
// 点击按钮提交数据
postApi() {
axios.post('http://localhost:3000/', {
item: this.item
}).then(res => {
console.log("\n【API - post 数据】")
console.log(res);
this.items = res.data;
}).catch(function (err) {
console.log(err)
})
}
}
})
script>
body>
html>
我们通过 Vue 进行了布局,通过 Axios 进行了接口的请求。从而完成了对数据的操作。
首先,我们通过可视化工具进行表的设计:
名 | 类型 | 长度 | 键 |
---|---|---|---|
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。
———————华丽分割线———————
当然,增删改查是后端的基本操作,所以在这里,我们可以补全基本的增删改查功能。
先看目录:
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
,就能往数据库中新增数据了。
// 连接 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();
// 连接 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();
// 连接 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 的壁垒,实现了数据的增删改查。
首先,我们查看下我们的前端基本代码:地址
然后,我们进行后端功能分析:
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 模块创建服务
/**
* req 获取 url 信息 (request)
* res 浏览器返回响应信息 (response)
*/
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>演示代码</title>
</head>
<body>
<div>
<label for="user">用户名</label><input type="text" id="user">
</div>
<div>
<label for="password">密 码</label><input type="password" id="password">
</div>
<div>
<button id="register">注册</button>
</div>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
<script>
$(function () {
// 测试 get 接口
$.ajax({
url: "http://localhost:8888/getMessage",
type: "POST",
data: {
username: "jsliang"
},
success: function (res) {
console.log(res);
},
error: function (err) {
console.log(err);
}
})
$("#register").click(function () {
// 测试 post 接口
$.ajax({
url: "http://localhost:8888/login",
type: "POST",
data: {
username: $("#user").val(),
password: $("#password").val()
},
success: function (res) {
console.log(res);
},
error: function (err) {
console.log(err);
}
})
})
});
</script>
</body>
</html>
最后,我们通过 node index.js
,并打开 index.html
,通过 F12
控制台查看我们的接口是否正常:
可以看到我们的接口能正常调通,这样我们就可以连接数据库,进行这 4 个接口的设计了。
如果小伙伴们觉得每次更新 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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="keywords" content="前端,jsliang,bootstrap,企业建站">
<meta http-equiv="description" content="jsliang 为你打造最好的企业服务">
<link rel="shortcut icon" href="./images/favicon.ico" type="image/x-icon" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>注册-jsliang 前端有限公司</title>
<link rel="stylesheet" href="./css/index.css">
<link rel="stylesheet" href="./css/bootstrap.min.css">
</head>
<body>
<!-- 省略 body 中代码,有需要的请前往第四章开头下载查看全部代码 -->
<script src="./js/jquery-3.3.1.min.js"></script>
<script src="./js/bootstrap.min.js"></script>
<script src="./js/islogin.js"></script>
<script>
$(function () {
$("#register-submit").click(function () {
let userName = $("#userName").val();
let userPassword = $("#userPassword").val();
if (!userName) {
alert("请输入用户名");
$("#userName").focus();
} else if (!userPassword) {
alert("请输入密码");
$("#userPassword").focus();
} else if (userName.length > 10) {
alert("请输入少于 10 位的用户名");
$("#userName").focus();
} else if (userPassword.length > 20) {
alert("请输入少于 20 位的密码");
$("#userPassword").focus();
} else {
// 如果用户输入的没毛病,那就加载接口
$.ajax({
url: "http://localhost:8888/register",
type: 'post',
dataType: 'json',
data: {
username: userName,
password: userPassword
},
success: function (res) {
console.log(res);
if (res.code == "0") {
alert("注册成功,前往登录!");
window.location.href = "./login.html";
}
},
error: function (err) {
console.log(err.responseText);
if (err.responseText == "注册失败,姓名重复!") {
alert("用户名已被注册!");
} else if (err.responseText == "注册失败,名额已满!") {
alert("注册失败,名额已满!");
} else if (err.responseText == "注册失败,密码为空!") {
alert("注册失败,密码为空!");
} else if (err.responseText == "注册失败,姓名过长!") {
alert("注册失败,姓名过长!");
} else if (err.responseText == "注册失败,密码过长!") {
alert("注册失败,密码过长!");
} else {
alert("未知错误!");
}
}
})
}
})
})
</script>
</body>
</html>
如此,我们在用户点击 注册 按钮的时候,进行接口的调用,发送数据到了后端,如果成功了,那就弹窗,并跳转到登录页;如果没成功,就弹窗提示。
接着,我们编写 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 结束
}
// 注册流程结束
}
最后,我们在查看下该功能是否成功:
在上面,我们完成了注册功能,那么相对来说,登录功能就容易通了,因为查询部分我们已经试过了一次。
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="keywords" content="前端,jsliang,bootstrap,企业建站">
<meta http-equiv="description" content="jsliang 为你打造最好的企业服务">
<link rel="shortcut icon" href="./images/favicon.ico" type="image/x-icon" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>登录-jsliang 前端有限公司</title>
<link rel="stylesheet" href="./css/index.css">
<link rel="stylesheet" href="./css/bootstrap.min.css">
</head>
<body>
<!-- 代码省略,有需要的小伙伴请在第四章前言部分下载代码 -->
<script src="./js/jquery-3.3.1.min.js"></script>
<script src="./js/bootstrap.min.js"></script>
<script src="./js/islogin.js"></script>
<script>
$(function () {
$("#login-submit").click(function () {
let userName = $("#userName").val(); // 用户名
let userPassword = $("#userPassword").val(); // 密码
if (!userName) {
alert("请输入用户名");
$("#userName").focus();
} else if (!userPassword) {
alert("请输入密码");
$("#userPassword").focus();
} else if (userName.length > 10) {
alert("请输入少于 10 位的用户名");
$("#userName").focus();
} else if (userPassword.length > 20) {
alert("请输入少于 20 位的密码");
$("#userPassword").focus();
} else {
$.ajax({
url: "http://localhost:8888/login",
type: 'post',
dataType: 'json',
data: {
username: userName,
password: userPassword
},
success: function (res) {
console.log(res);
if (res.code == "0") {
sessionStorage.setItem("id", res.data.id);
sessionStorage.setItem("userName", res.data.userName);
alert("登录成功!");
window.location.href = "./messageBoard.html";
} else if (res.code == "1") {
alert("登录失败,密码错误!");
}
},
error: function (err) {
console.log(err.responseText);
if (err.responseText == "不存在该用户!") {
alert("不存在该用户!");
} else if (err.responseText == "登录失败,用户名为空!") {
alert("登录失败,用户名为空!");
} else if (err.responseText == "登录失败,密码为空!") {
alert("登录失败,密码为空!");
} else if (err.responseText == "登录失败,姓名过长!") {
alert("登录失败,姓名过长!");
} else if (err.responseText == "登录失败,密码过长!") {
alert("登录失败,密码过长!");
} else {
alert("未知错误!");
}
}
})
}
})
})
</script>
</body>
</html>
编写完前端的代码后,我们进行 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 - 注册】");
}
很好,前端和后端都编写完毕,是时候查验下功能是否实现了:
现在,我们就剩下留言功能了,一鼓作气做好它吧!
messageBoard.html
<!-- 留言板 -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="keywords" content="前端,jsliang,bootstrap,企业建站">
<meta http-equiv="description" content="jsliang 为你打造最好的企业服务">
<link rel="shortcut icon" href="./images/favicon.ico" type="image/x-icon" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>留言板-jsliang 前端有限公司</title>
<link rel="stylesheet" href="./css/index.css">
<link rel="stylesheet" href="./css/bootstrap.min.css">
</head>
<body>
<!-- 代码省略,基础代码请前往本章节前言下载 -->
<script src="./js/jquery-3.3.1.min.js"></script>
<script src="./js/bootstrap.min.js"></script>
<script src="./js/islogin.js"></script>
<script>
$(function() {
let userName = sessionStorage.getItem("userName");
let userId = sessionStorage.getItem("id");
// 查询留言板
if(userName && userId) { // 如果有存储
$.ajax({
url: "http://localhost:8888/getMessage",
type: 'get',
dataType: 'json',
success: function (res) {
console.log(res);
let li = ``;
for(let item in res.data) {
li = li + `
-
☆
${res.data[item].user_message}
——
${res.data[item].user_name} [${res.data[item].user_id}]
${res.data[item].time}
`;
}
$("#message-board-ul").append(li);
},
error: function (err) {
console.log(err);
}
})
} else { // 如果没有存储
window.location.href = "../login.html";
}
// 提交留言
$("#message-submit").click(function() {
let messageText = $("#message").val()
if(!messageText) {
alert("留言内容不能为空");
} else if(messageText.length > 140) {
alert("留言长度不能超过 140 位!");
} else {
$.ajax({
url: "http://localhost:8888/sendMessage",
type: 'post',
dataType: 'json',
data: {
userid: userId,
username: userName,
message: messageText
},
success: function (res) {
console.log(res);
if(res.code == "0") {
alert("新增成功!");
window.location.reload();
}
},
error: function (err) {
console.log(err);
console.log(err.responseText);
if (err.responseText == "登录失败,留言内容为空!") {
alert("登录失败,留言内容为空!");
} else if (err.responseText == "登录失败,字数超过限制!") {
alert("登录失败,字数超过限制!");
} else {
alert("未知错误!");
}
}
})
}
})
})
</script>
</body>
</html>
接着编写下 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();
}
敲完代码再看下功能是否实现:
综上,我们完成了所有的功能模块:注册、登录以及留言。
正如其官网所说,它是一个进行控制系统:
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 的常用的功能和配置:
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