栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

【学习笔记】Node--从0基础到实战企业官网

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【学习笔记】Node--从0基础到实战企业官网

前言:
    本文是学习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进程管理
            • 参考资料
一、基础学习 1.1 HTTP–开始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之旅?

1.2 URL模块

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

  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 删除文件 。

此章节的文件目录:

首先,通过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("剪切成功!");
  }
})

通通搞定,现在目录变成了:

1.5 fs案例

在上一章节中,我们了解了 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 模块

1.9 get与post


话不多说,先上代码:
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

首先,我们通过可视化工具进行表的设计:

名类型长度键
idint11主键
namevarchar255
agevarchar255

然后,我们进行表的填充:

idnameage
1jsliang23
2liang25

接着,我们安装 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 编程环境

首先,我们查看下我们的前端基本代码:地址

然后,我们进行后端功能分析:

  1. 留言板。用户点击 留言板 的时候,需要先判断用户是否登录。如果用户尚未登录,则直接跳转到 登录页;如果用户登录了,则显示 留言板页面。
    在 留言板页面 中,存在两个接口:
  • 获取留言内容: 调取 getMessage 接口,返回全部留言信息,由于预计信息不多,故这里不做分页功能,有需要的小伙伴在实现完这个功能后,可以进行分页接口的设计;
  • 提交留言内容:调取 sendMessage 接口,将用户名、用户 id、留言内容发送给后端。
  1. 在 登录页面 中,存在一个接口:
    登录:调取 login 接口,提交用户填写的姓名和密码。
  2. 在 注册页面 中,存在一个接口:
    注册:调取 register 接口,提交用户填写的姓名和密码。

由此,我们可以设计下前后端的接口结合:

接口文档

接口类型参数返回信息
getMessage:获取留言信息get无参n 条记录:id(用户 id)、user_name(用户名)、user_message(用户留言内容)、time(留言时间)
sendMessage:提交留言信息postid(用户 id)、user_name(用户名)、user_message(用户留言内容)status 状态
login:登录postid(用户 id)、user_name(用户名)、user_password(用户密码)status 状态
register:注册postid(用户 id)、user_name(用户名)、user_password(用户密码)status 状态

最后,我们进行 MySQL 数据库的表设计:

user 表

名类型长度键
idint11主键
user_namevarchar255
user_passwordvarchar255
timedatetime

message 表

名类型长度键
idint11主键
user_namevarchar255
user_passwordvarchar255
timedatetime
2.2 后端接口

在我们进行实操之前,先确认我们是否能写接口,所以我们可以新建一个 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 个接口的设计了。

如果小伙伴们觉得每次更新 Node 代码后,又要重启一遍 node index.js 觉得麻烦,可以通过 supervisor 来监听 Node 代码的改动

2.3 注册功能

很好,我们回到仿企业网站的页面上,准备编写接口以及丰富 Node 的接口。
首先,我们开启前端和 Node 服务:

  1. 打开命令行/终端 ;
  2. 开启前端
    cd FrontEndCode
    live-server

安装 live-server:npm i live-server -g

  1. 开启后端
    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 代码的变动。

3.2 PM2 – Node进程管理

PM2是Node进程管理工具,可以利用它来简化很多Node应用管理的繁琐任务,如性能监控、自动重启、负载均衡等,而且使用非常简单。
下面就对 PM2 进行入门性的介绍,基本涵盖了 PM2 的常用的功能和配置:

  1. 全局安装PM2 : npm i pm2 -g
  2. 监听应用 : pm2 start index.js
  3. 查看所有进程:pm2 list
  4. 查看某个进程: pm2 describe App name/id
  5. 停止某个进程: pm2 stop App name/id 。例如:

先通过 pm2 list 查看:

App nameidstatus
index0online

只需要执行 pm2 stop index 或者 pm2 stop 0 即可。

  1. 停止所有进程: pm2 stop all
  2. 重启某个进程 : pm2 restart App name/id
  3. 删除某个进程 : pm2 delete App name/id

如上,如果说我们的 supervisor 是监听单个进程的话,那么 PM2 就是监听多个进程。

参考资料

Node - 从0基础到实战企业官网 https://juejin.cn/post/6844903745755545614#heading-12

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1024202.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号