大家好,我是编程小6,很高兴遇见你,有问题可以及时留言哦。
Create by jsliang on 2018-11-8 13:42:42
Recently revised in 2018-12-23 21:59:20
Hello 小伙伴们,如果觉得本文还不错,记得点个赞或者给个 star,你们的赞和 star 是我编写更多更精彩文章的动力!GitHub 地址
【2019-08-16】Hello 小伙伴们,由于 jsliang 对文档库进行了重构,这篇文章的一些链接可能失效,而 jsliang 没有精力维护掘金这边的旧文章,对此深感抱歉。请需要获取最新文章的小伙伴,点击上面的 GitHub 地址,去文档库查看调整后的文章。
不折腾的前端,和咸鱼有什么区别
目录 |
---|
一 目录 |
二 前言 |
三 基础学习 |
3.1 HTTP - 开始 Node 之旅 |
3.2 URL 模块 |
3.3 CommonJS |
3.4 包与 npm |
3.5 fs 文件管理 |
3.6 fs 案例 |
3.7 fs 流 |
3.8 创建 Web 服务器 |
3.9 非阻塞 I/O 事件驱动 |
3.10 get 与 post |
3.11 Node 连接 MySQL |
四 Web 实战 —— 企业官网 |
4.1 编程环境 |
4.2 后端接口 |
4.3 注册功能 |
4.4 登录功能 |
4.5 留言功能 |
五 工具整合 |
5.1 supervisor - 监听 Node 改动 |
5.2 PM2 - Node 进程管理 |
六 参考资料 |
七 线上部署 |
八 归纳总结 |
返回目录
本文主要目的:
798961601
中咨询。返回目录
万丈高楼平地起,地基还得自己起。
返回目录
话不多说,先上代码:
01_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('<h1 style="text-align:center">Hello NodeJS</h1>');
// 结束响应
res.end();
}).listen(3000); // 监听的端口
那么,上面代码,我们要怎么用呢?
首先,将上面的代码复制粘贴到 01_http.js
中。
然后,启动 VS Code 终端:Ctrl + ~
。
接着,输入 node 01_http.js
并回车。
最后,打开 localhost:3000
:
OK,搞定完事,现在我们一一讲解上面代码:
var http = require("http");
/** * req 获取 url 信息 (request) * res 浏览器返回响应信息 (response) */
http.createServer(function (req, res) {
// ... 步骤 3 代码
}).listen(3000); // 监听的端口
// 设置 HTTP 头部,状态码是 200,文件类型是 html,字符集是 utf8
res.writeHead(200, {
"Content-Type": "text/html;charset=UTF-8"
});
// 往页面打印值
res.write('<h1 style="text-align:center">Hello NodeJS</h1>');
// 结束响应
res.end();
http://localhost:3000/
,将访问到我们开启的 Node 服务,从而往页面渲染页面。至此,小伙伴们是不是也开启了自己的 Node 之旅?
返回目录
URL 模块是什么呢?
我们在控制台(终端)开启 Node 模式,并打印出 url
来看一下:
好家伙,它有 Url
、parse
、resolve
、resolveObject
、format
、URL
、URLSearchParams
、domainToASCII
、domainToUnicode
这么多模块。
那么,这些模块都有什么用呢?
话不多说,先上代码:
02_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('<h1 style="text-align:center">Hello NodeJS</h1>');
// 结束响应
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('<h1 style="text-align:center">Hello NodeJS</h1>');
// 结束响应
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
resolve
的使用:console.log(url.resolve("http://www.baidu.com/jsliang", "梁峻荣"));
// Console:
// http://www.baidu.com/梁峻荣
当然,url
这里我们只讲解了个入门,更多的还请看官网 API:url | Node.js v10.14.1 文档
返回目录
CommonJS 就是为 JS 的表现来制定规范,因为 JS 没有模块系统、标准库较少、缺乏包管理工具,所以 CommonJS 应运而生,它希望 JS 可以在任何地方运行,而不只是在浏览器中,从而达到 Java、C#、PHP 这些后端语言具备开发大型应用的能力。
CommonJS 就是模块化的标准,Node.js 就是 CommonJS(模块化)的实现。
现在,我们通过三种使用方式,来讲解下 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('<h1 style="text-align:center">Hello NodeJS</h1>'); 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 就很靠谱,它规范我们可以将这些文件都放在 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 = sum * numbers[number];
}
return sum;
}
}
module.exports = tools;
在引用方面,我们只需要通过:
// 如果 Node 在当前目录没找到 tool.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
,然后将我们的 tools.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:\MyWeb\node_modules\jsliang-module> 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('<h1 style="text-align:center">Hello NodeJS</h1>');
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 模块化的概念啦~
参考文献:
返回目录
Node 中除了它自己提供的核心模块之外,还可以自定义模块,以及使用 第三方模块。
Node 中第三方模块由包组成,可以通过包来对一组具有相互依赖关系的模块进行统一管理。
那么,假如我们需要使用一些第三方模块,应该去哪找呢?
那么,npm 是啥?
npm 是世界上最大的开放源代码的生态系统。我们可以通过 npm 下载各种各样的包。
在我们安装 Node 的时候,它默认会顺带给你安装 npm。
npm -v
:查看 npm 版本。npm list
:查看当前目录下都安装了哪些 npm 包。npm info 模块
:查看该模块的版本及内容。npm i 模块@版本号
:安装该模块的指定版本。在平时使用 npm 安装包的过程中,你可能需要知道一些 npm 基本知识:
i
/install
:安装。使用 install
或者它的简写 i
,都表明你想要下载这个包。uninstall
:卸载。如果你发现这个模块你已经不使用了,那么可以通过 uninstall
卸载它。g
:全局安装。表明这个包将安装到你的计算机中,你可以在计算机任何一个位置使用它。--save
/-S
:通过该种方式安装的包的名称及版本号会出现在 package.json
中的 dependencies
中。dependencies
是需要发布在生成环境的。例如:ElementUI
是部署后还需要的,所以通过 -S
形式来安装。--save-dev
/-D
:通过该种方式安装的包的名称及版本号会出现在 package.json
中的 devDependencies
中。devDependencies
只在开发环境使用。例如:gulp
只是用来压缩代码、打包的工具,程序运行时并不需要,所以通过 -D
形式来安装。例子:
cnpm i webpack-cli -D
npm install element-ui -S
那么,这么多的 npm 包,我们通过什么管理呢?
答案是 package.json
。
如果我们需要创建 package.json
,那么我们只需要在指定的包管理目录(例如 node_modules
)中通过以下命名进行生成:
npm init
:按步骤创建 package.json
。npm init --yes
:快速创建 package.json
当然,因为国内网络环境的原因,有些时候通过 npm 下载包,可能会很慢或者直接卡断,这时候就要安装淘宝的 npm 镜像:cnpm
npm install -g cnpm --registry=https://registry.npm.taobao.org
返回目录
本章节我们讲解下 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
// 1. fs.stat
let fs = require('fs');
fs.stat('index.js', (error, stats) => {
if(error) {
console.log(error);
return false;
} else {
console.log(stats);
/**
* Console:
* Stats {
* dev: 886875,
* mode: 33206,
* nlink: 1,
* uid: 0,
* gid: 0,
* rdev: 0,
* blksize: undefined,
* ino: 844424931461390,
* size: 284,
* blocks: undefined,
* atimeMs: 1542847157494,
* mtimeMs: 1543887546361.2158,
* ctimeMs: 1543887546361.2158,
* birthtimeMs: 1542847157493.663,
* atime: 2018-11-22T00:39:17.494Z,
* mtime: 2018-12-04T01:39:06.361Z,
* ctime: 2018-12-04T01:39:06.361Z,
* birthtime: 2018-11-22T00:39:17.494Z }
*/
console.log(`文件:${stats.isFile()}`);
// Console:文件:true
console.log(`目录:${stats.isDirectory()}`);
// Console:目录:false
return false;
}
})
通过 Console
打印出来的信息,我们基础掌握了 fs.stat
的作用。
然后,我们尝试通过 fs.mkdir
创建目录:
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
读取目录05_fs.js
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:
// 读取文件成功!
// <Buffer 48 65 6c 6c 6f 20 6a 73 6c 69 61 6e 67 e8 bf 99 e6 ae b5 e6 96 87 e6 9c ac e6 98 af e8 a6 81 e8 bf bd e5 8a a0 e7 9a 84 e5 86 85 e5 ae b9>
}
})
// 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("剪切成功!");
}
})
OK,通通搞定,现在目录变成了:
返回目录
在上一章节中,我们了解了 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
这三种文件类型而已,我们需要模拟更多的文件类型:
08_ext.json
代码详情请点击上面的链接
在上面的 json
文件中,我们定义了各种的文件类型,此刻文件目录如下所示:
这时候,我们需要修改下我们的 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 万用户的连接。
在这一章节中,主要解决:
首先,在我们正常编程中,我们是希望程序能够一行一行按照我们的意愿编写的:
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 驱动。
那么,我们有没有办法解决这个问题呢?
有的!
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 Web</title>
</head>
<body>
<div id="app">
<h1>Todo List</h1>
<ul>
<li v-for="(item, index) in items" :key="index">{{ item }}</li>
</ul>
<input type="text" v-model="item">
<button @click="postApi">添加</button>
</div>
<!-- cdn 引用:Vue 和 Node -->
<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 进行了接口的请求。从而完成了对数据的操作。
返回目录
关于 MySQL 的安装,可以查看 jsliang 写的:MySQL 安装及图形化工具
首先,我们通过可视化工具进行表的设计:
名 | 类型 | 长度 | 键 |
---|---|---|---|
id | int | 11 | 主键 |
name | varchar | 255 | |
age | varchar | 255 |
然后,我们进行表的填充:
id | name | age |
---|---|---|
1 | jslliang | 23 |
2 | 梁峻荣 | 23 |
接着,我们安装 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: '梁峻荣', age: '23' } ]
如此,我们便完成了 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 的壁垒,实现了数据的增删改查。
返回目录
在进行代码实战的时候,我们很多时候会遇到一些小事儿,例如:logo 制作、ico 制作、icon 挑选等……
下面这些都是 jsliang 平时碰到的,小伙伴有需要的可以 mark 啦~
另外,由于 HTML 与 CSS 没什么好讲的,所以本章节的前提静态页面 jsliang 已经写好了,小伙伴们在学习前可以预先下载:
返回目录
首先,我们查看下我们的前端基本代码:地址
如上,我们仅需要了解 FrontEndCode 目录以及 NodeWeb 目录即可,其他目录为上面章节练习参考。
然后,我们进行后端功能分析:
在 留言板页面 中,存在两个接口:
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_message | varchar | 255 | |
user_id | varchar | 255 | 外键 |
user_name | varchar | 255 | |
time | datetime |
返回目录
在我们进行实操之前,先确认我们是否能写接口,所以我们可以新建一个 test
文件夹,里面放一个 index.html
以及一个 index.js
来测试一下。
- text
- index.html
- index.js
首先,我们就 4.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('<h1 style="text-align:center">jsliang 前端有限公司服务已开启!</h1><h2 style="text-align:center">详情可见:<a href="https://github.com/LiangJunrong/document-library/blob/master/other-library/Node/NodeBase.md" target="_blank">Node 基础</a></h2>');
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 代码的改动,supervisor
的安装使用:supervisor
返回目录
很好,我们回到仿企业网站的页面上,准备编写接口以及丰富 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
// ... 其他代码省略,请自行前往章节 4.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
// ... 其他代码省略,请自行前往章节 4.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 + ` <li> <span class="text-warning font-bold">☆ </span> <span class="user-message">${res.data[item].user_message}</span> <span>—— </span> <span class="user-name">${res.data[item].user_name} [${res.data[item].user_id}]</span> <span class="message-time">${res.data[item].time}</span> </li> `; } $("#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
// ... 其他代码省略,请自行前往章节 4.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 - 注册】");
}
// ... 其他代码省略,请自行前往章节 4.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('<h1 style="text-align:center">jsliang 前端有限公司服务已开启!</h1><h2 style="text-align:center">详情可见:<a href="https://github.com/LiangJunrong/document-library/blob/master/other-library/Node/NodeBase.md" target="_blank">Node 基础</a></h2>');
res.end();
}
敲完代码再看下功能是否实现:
综上,我们完成了所有的功能模块:注册、登录以及留言。
返回目录
工欲善其事,必先利其器。
掌控好了工具,可以方便你更快地进行开发。
返回目录
正如其官网所说,它是一个进行控制系统:
npm i supervisor -g
supervisor app.js
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
就是监听多个进程。
更多攻略:
PM2 官网
PM2 用法简介 | 简书 - LeavesLife
PM2实用入门指南 | 博客园 - 程序猿小卡
返回目录
在编写这篇文章的过程中,有一些参考资料是值得保留阅读的:
经典,就是随着时间流逝,它还是那么有参考价值。
Node 基础模块
Node 编写接口
MySQL 学习
Node 连接数据库
Node 仿 Express
nodejs模块:简单http请求路由,仿express | CSDN - TTUZ
初学nodejs一:别被Express的API搞晕了 | 前端乱炖 - 飞天小黑神猪
NodeJs 实战——原生 NodeJS 轻仿 Express 框架从需求到实现(一) | 倔强的石头 - 掘金
NodeJs 实战——原生 NodeJS 轻仿 Express 框架从需求到实现(二) | 倔强的石头 - 掘金
仿 Express | Github - wallaceyuan
Node.js 封装仿照 express 的路由 | CSDN - c.
学习node中express框架中间件的相关知识及实践 | Github - BadWaka
返回目录
关于线上部署及域名、服务器相关的配置,jsliang 在另外一篇文章有所交代:云服务器建站。
如果小伙伴需要订购云服务器来存放像 jsliang 个人网站类的静态或者有 Node 后端的网页,但却不知道怎么选择,可以加 jsliang QQ:1741020489
咨询,下面是一些优惠推广:
腾讯云推广:
新用户点这里:
购买云服务器:
阿里云推广:
新用户点这里:
购买云服务器:
购买企业级云服务器:
返回目录
综上,搞定一切!
兴许在前面代码的摧残下,能看到这里的小伙伴已经寥寥无几了,但我坚信我该交代的基本都交代了,不该交代的也交代了~
所以,如果小伙伴看完真觉得不错,那就点个赞或者给个 star 吧!你们的赞和 star 是我编写更多更精彩文章的动力!GitHub 地址
如果小伙伴看完这里要评论的话,可以加个暗语:Node 基础,***
,这样 jsliang 看到必回,哈哈~
so, that's all, thanks~
-----------------------
撰文不易,如果文章对小伙伴有帮助,希望小伙伴们给勤劳敲代码、辛苦撰文的 jsliang 进行微信/支付宝打赏,你们的每一次打赏都是最好的鼓励,谢谢~
jsliang 的文档库 由 梁峻荣 采用 知识共享 署名-非商业性使用-相同方式共享 4.0 国际 许可协议进行许可。
基于github.om/LiangJunron…上的作品创作。
本许可协议授权之外的使用权限可以从 creativecommons.org/licenses/by… 处获得。