admin管理员组

文章数量:1547451

文章目录

      • Node.js基础
        • 1.Node.js前言
        • (1)Node出现的背景
        • (2)V8引擎
        • 2.Node.js简介
        • (1)什么是Node.js
        • (2)Node.js的特点
        • (3)Node.js的应用方向
      • 客户端与服务器
        • 1.CS与BS
        • 2.Web资源
        • 3.资源访问流程
      • Http协议
        • 1.什么是协议
        • 2.什么是HTTP协议
        • 3.HTTP的组成部分
        • 4.请求的发送方式
        • 5.Http请求
        • 6.Http响应
        • 7.请求方式
      • node.js基础
        • 1.命令行窗口
        • 2.进程和线程
        • 3.安装node.js
        • 4.node如何运行js程序
        • 5.Node的模块化开发思想
        • 6.exports和module.exports的区别
        • 7.包和包管理器
      • node.js基础-文件系统
        • 1.Buffer(缓冲区)
        • 2.文件系统(File System)
      • Node基础-MongoDB
        • 1.数据库(Database)
        • 2.MongoDB
        • 3.集合间关系
        • 4.排序和索引
      • Node进阶-Mongoose
        • 1.需求分析
        • 2.mongoose的基本使用
        • 3.mongoose对文档进行增删改查
      • Node进阶-常用模块
        • 1.前言引入
        • 2.HTTP-路由(HTTP模块)
        • 3.URL模块
        • 4.HTTP-表单提交(HTTP模块)
        • 5.静态资源库
        • 6.模版引擎
      • Node进阶-Express
        • 1.Express简介
        • 2.Express-基本使用
        • 3.Express-搭建静态资源库
        • 4.Express-路由使用
        • 5.Express-模版引擎
        • 6.Express-应用生成器
        • 7.Express-中间件

Node.js基础

1.Node.js前言
  • Node.js之父:Ryan Dahl(瑞安达尔)
(1)Node出现的背景
  • Node出现的背景:为了解决Web服务器的高并发性能问题。

  • 什么是性能问题?

    ① 完整的HTTP请求图如下:

② 性能问题简单来说就是,由于发送请求到接收到响应的时间太长而出现的一些问题。

③ 缩短发送到响应的时长的方法:

1.发送请求快一点(不能控制,因为是由客户端的网速决定的)
2.响应快一点(可以控制,可以通过提高服务器带宽或利用CDN加速等方法实现,但是有限制,不可能无限加快)
3.服务器处理请求任务快一点:(1)可以控制,取决于程序员的技术;(2)多线程(发送一个请求就开启一条线程)
4.服务器从磁盘读取/写入数据到数据库快一点,可以通过分布式、矩阵式、刀片式等方法提高速度。但因为磁盘的读取速度都是有上限的,所以后期都会遇到瓶颈,都会遇到I/O阻塞。
  • Ryan Dahl为了解决I/O阻塞,尝试用Ruby、c、Lua去解决,但都因为语言自身的各种限制(语言的历史包袱太重;各种语言的思想都根深蒂固,生态很难改变等)而一一失败。最终通过慢慢摸索,找到了解决问题的方法:事件驱动和异步I/O(V8引擎就满足这两个特性,所以V8引擎的出现是至关重要的)。
(2)V8引擎
  • V8引擎的定义:V8引擎是一款专门对JavaScript语言进行解释和执行的流程虚拟机。比如:如果把V8引擎嵌入到浏览器中,那么JavaScript代码就会被浏览器所执行;如果把V8引擎嵌入到NodeJS环境下,那么JavaScript代码就会被服务器所执行。(万能的JS的定义:主要把V8引擎能够嵌入到不同的宿主环境中,那么就可以用JavaScript语言来写各种不同领域的应用。)

  • V8引擎起初的作用:用于Chrome浏览器解析js脚本。

  • V8引擎的优势:

① 强大的编译和快速执行效率。(因为运用了大量的算法和奇技淫巧)

② 性能非常好,它的执行效率远超Python、Ruby等其它脚本语言。

③ 历史包袱轻,没有同步I/O。

④ 强大的事件驱动机制。

  • Node的诞生:Ryan Dahl修改了V8引擎的内核,把它用在了服务器开发,经过修改后的这样一套东西就被称为Node.js(简化版的Node.js,它只是为了解决Web服务器的性能问题)。
2.Node.js简介
(1)什么是Node.js
  • Node.js是一个让JavaScript运行在服务器端的开发平台。

    Node.js该开发平台的演化过程:

    ① Node之前,js代码只能运行在客户端。

    ② Node之后,js代码可以和操作系统(Mac OS,windows,Linux…)交互,战场从浏览器延伸到服务器。

    ③ 版本的变化表如下所示:

    Node一开始是叫做Web.js,目的就是用于写高性能Web服务器的,后来越写越大,逐渐形成自己的生态(服务器开发,各种框架的依赖…),因此改名为Node.js。

    时间节点发生的事件
    2009年瑞安达尔在GitHub上发布Node的初始版本
    2010年1月Node包管理器NPM上线
    2010年12月Joyent赞助Node开发,瑞安达尔加入全力负责Node开发
    2011年7月与微软合作,发布了Node的Windows版本
    2012年1月瑞安达尔退出Node团队
    2014年12月Fedor Indutuy开源分支版本,起名为“io.js”
    2015年1月Node基金会成立(IBM,Intel,微软,Joyent)
    2015年9月Node.js和io.js合并
    2016年Node.js第6.0版本发布
    2017年Node.js第8.0版本发布
    2018年Node,js第8.x同步更新
    2019年
  • Node.js与其它后端语言(PHP、JSP、Python、Ruby等)功能类似,都能和系统进行交互。

  • Node.js与其它后端语言的区别:

    ① Node.js不是一种独立的语言,它只是一个开发平台。并且它是用JavaScript进行编程,运行平台是包装后的js引擎(V8)。而其它后端语言,像PHP、JSP…语言,它们既是语言,也是平台。

    ② Node.js是轻量级架构,它不用架设在任何服务器软件(Web容器)上,它可以用最小的硬件成本,达到更高的并发,更优的处理性能。而其它后端语言,像Java、PHP、等都需要运行在服务器(apache、tomcat、naginx、IIS等)上。

(2)Node.js的特点

① 单线程。

优势:减少了内存开销(操作系统完全不再有线程创建、销毁的时间开销)。在Java、PHP或者等服务器语言中,会为每一个客户端连接创建一个新的线程,而每个线程需要耗费大约2MB内存,相当于有一个人就需要分配一个线程,就要占用空间大小。而Node.js不会为每个客户端连接创建一个新的线程,而仅仅使用一个线程。当有用户连接了,就会触发一个内部事件,通过非阻塞I/O、事件驱动机制,让Node.js程序宏观上也是并行的。使用Node.js,一个8GB内存的服务器,可以同时处理超过4万用户的连接。

劣势:如果某一个事情进入了,但是被I/O阻塞了,那么整个线程也会被阻塞;如果一个人把Node.js搞崩溃,那么全部都将崩溃(虽然实际是很难搞崩溃的)。

② 非阻塞I/O。

当在访问数据库取得数据的时候,可能需要一段时间。在传统的单线程处理机制中,在执行了访问数据库代码之后,整个线程都将暂停下来,等待数据库的返回结果,才能执行后面的代码。也就是说,I/O阻塞了代码的执行,极大地降低了程序的执行效率。在阻塞模式下(即传统处理方法),一个线程只能处理一项任务,要想提高吞吐量必须通过多线程;而非阻塞模式下,一个线程永远在执行计算操作,这个线程的CPU核心利用率永远是100%。Node.js中就是采用了非阻塞I/O机制,因此在执行了访问数据库的代码之后,将立即转而执行其后面的代码,把数据库返回结果的处理代码放在回调函数中,从而提高了程序的执行效率。当某个I/O执行完毕时,将以事件的形式通知执行I/O操作的线程,线程执行这个事件的回调函数。为了处理异步I/O,线程必须有事件循环,不断的检查有没有未处理的事件,依次予以处理。

③ 事件驱动。

基本概念:不管是新用户的请求,还是老用户的I/O完成,都将以事件方式加入事件环,等待调度。

运作流程:

注意:Node.js当中的所有I/O都是异步的,都是回调函数套回调函数。

(3)Node.js的应用方向
  • 特点:

    ① 善于I/O,不善于计算。

    因为Node.j最擅长的就是任务调度,如果业务中有很多CPU计算,实际上也相当于这个计算阻塞了这个单线程,就不适合Node开发;如果应用程序需要处理大量并发的I/O,并且在向客户端发出响应之前,应用程序内部并不需要进行非常复杂的处理的时候,Node.js就非常适合。Node.js也非常适合与web socket配合,开发长连接的实时交互应用程序。

    ② 天生异步。

    callback:Node.js API与生俱来就是这样的。

    thunk:参数的求值策略。

    promise:最开始上Promise/A+规范,随后成为ES6标准。

    generator:ES6中的生成器,用于计算,但tj想用做流程控制。

    co:generator用起来非常麻烦,故而tj写了co这个generator生成器用法更简单。

    async函数。

  • 适用场景:

    网站开发(如express/koa等)

    im即时聊天(socket.io)

    api(移动端、pc、h5)

    HTTP Proxy(淘宝、Qunar、腾讯、百度都有)

    前端构建工具(grunt/gulp/bower/webpack/fis3…)

    跨平台打包工具

    写操作系统(NodeOS)

    命令行工具(比如cordova、shell.js)

    反向代理(比如anyproxy、node-http-proxy)

    编辑器Atom、VSCode等

  • Node.js不是全能的。

    Node.js本身就是极客追求性能极致的产物,缺少了很多服务器的健壮考量,所以Node不可能应用在银行、证券、电信等需要极高可靠性的业务中。而在中国的企业实战中,创业型公司(正处于A轮、B轮)非常喜欢使用Node做核心业务。

  • 在大企业中使用的场景:

客户端与服务器

1.CS与BS
  • CS和BS是软件使用方式上的两种划分。

  • C/S(Client/Server):即PC客户端、服务器架构。

    特点:在服务器当中最主要的是一个数据库,把所有的业务逻辑以及界面都交给客户端完成。

    优点:较为安全,用户界面丰富,用户体验好。

    缺点:每次升级都要重新安装,要针对不同的操作系统开发,可移植性差。

  • B/S(Browser/Server):即浏览器/服务器架构。

    特点:B/S是基于浏览器访问的应用,它把业务层交给服务器来完成,客户端仅仅做界面的渲染和数据的交换。

    优点:只开发服务器端,可以跨平台、移植性很强。

    缺点:安全性比较低,用户体验较差。

2.Web资源
  • Web资源的定义:表示网络主机上供外界访问的资源。

  • Web资源的分类:

    ① 静态Web资源:指web页面中供人们浏览的数据始终是不变的。

    ② 动态Web资源:指web页面中供人们浏览的数据是由程序产生的,不同时间点访问web页面看到的内容各不相同。

  • Web资源的存放位置:所有的web资源都放在一个web服务器当中。其中,web服务器就是可以供外界访问web资源的一个软件,比如:apache、tomcat等。而web资源只要放到指定的目录当中,就可以通过对应的端口在浏览器当中访问到。

  • URL地址:协议://主机地址:端口号/资源地址,比如:http://www.bin:80/index.html

3.资源访问流程
  • 客户端:浏览器、Android程序、iOS程序、微信小程序。

  • 服务器:php服务器、tomcat服务器、nodeJS服务器…。

  • 通过浏览器访问一个网址时,能看到一个页面的原因:

    一个网址对应的其实就是一个IP地址(通过DNS),而一个IP地址对应一台电脑。所以通过IP地址就可以找到对应的电脑,电脑中安装有web服务器(可能有多个,比如:同时安转了apache和tomcat),通过端口号就可以找到对应的服务器。找到对应的服务器后,服务器就可以把页面返回给客户端。

  • BS结构流程图(http请求过程):

  • 请求与响应:

    ① 请求:把客户端请求发送给服务器。

    ② 响应:服务器把你要的数据发送给客户端。

    ③ 请求与响应都要遵循一定的格式:这个格式就是HTTP协议,它约定好客户端以什么样的格式把数据给服务器,约定好服务器以什么样的格式把数据给客户端。

Http协议

1.什么是协议
  • 协议就是约束双方规范的一个准则。
2.什么是HTTP协议
  • HTTP(超文本传输协议):是互联网上应用最为广泛的一种网络协议,所有的www文件都必须遵守这个标准,比如:http:www.baidu。
  • 设计HTTP最初的目的是为了提供一种发布和接收HTML页面的方法。后来结果不断的修正和完善,逐渐变成约束请求和响应的规则。
3.HTTP的组成部分
  • 请求。

  • 响应。

  • 请求与响应都是成对出现的:

4.请求的发送方式

① 通过浏览器的地址栏

② 通过html当中的form表单

③ 通过a链接的href

④ src属性

5.Http请求
  • 请求行

    ① 请求方式:POST或GET

    ② 请求资源:如:/request/index.html?username=andy&pwd=123

    ③ 协议版本:HTTP/1.0和HTTP/1.1。

    HTTP/1.0,发送请求,创建一次连接,获得一个web资源,连接断开。

    HTTP/1.1,发送请求,创建一次连接,获得多个web资源,保持连接。

  • 请求头

    ① 请求头是客户端发送给服务器端的一些信息。

    ② 使用键值对表示key:value。

    ③ 自动的把客户端的信息发送给服务器。

  • 请求体

    当请求方式是post时,请求体会有请求的参数;当请求方式是get时,请求参数不会出现在请求体中,会拼接在url地址后面。

  • 示意图:

6.Http响应
  • 响应行

    ① Http协议(协议版本)

    ② 状态码:

    200:请求成功

    302:请求重定向

    304:请求资源没有改变,访问本地缓存

    404:请求资源不存在。通常是用户路径编写错误,也可能是服务器资源已删除

    500:服务器内部错误。通常程序抛异常

    ③ 其它状态码:200~300之间的代表成功;300~400之间的代表重定向;400~500之间的代表客户方错误;500以上的代表服务器错误。

  • 响应头

    ① 服务器将信息以键值对的形式返回给客户端。

    ② 自动的把服务器端的信息传给客户端。

  • 响应体

    响应体是服务器回写给客户端的页面正文。浏览器接收到该页面正文后会把它加载到内存中,然后在解析渲染到页面上。

  • 示意图:

7.请求方式
  • 8种请求类型:

    ① OPTIONS:返回服务器针对特定资源所支持的HTTP请求方法,也可以利用向web服务器发送‘*’的请求来测试服务器的功能性。

    ② HEAD:请求指定的页面信息,并返回头部信息。

    ③ GET:请求指定的页面信息,并返回实体主体。

    ④ POST:向指定资源提交数据进行处理请求。

    ⑤ PUT:向指定资源位置上传其最新内容。

    ⑥ DELETE:请求服务器删除Request-URL所标识的资源。

    ⑦ TRACE:回显服务器收到的请求,主要用于测试或诊断。

    ⑧ CONNECT:HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。

  • 常用的2种请求:

    (1)GET:

    ① GET方法向页面请求发送参数

    ② 地址和参数信息中间用?字符分隔。比如:http://www.baidu/hrllo?username=andy&pwd=123

    ③ 查询字符串会显示在地址栏的URL中,不安全,请不要使用GET请求提交敏感数据

    ④ GET方法有大小限制:请求字符串中最多只能有1024个字符

    ⑤ GET请求能够被缓存

    ⑥ GET请求会保存中浏览器的浏览记录中

    ⑦ 可以添加书签

    ⑧ 编码类型为application/x-www-form-urlencoded

    ⑨ 只允许ASCII字符类型,不能用二进制流

    ⑩ 点击刷新,不会有反应

    ⑪ GET请求主要用于获取数据

    (2)POST:

    ① POST方法向页面请求发送参数

    ② 使用POST方法时,查询字符串在POST信息中单独存在,和HTTP请求一起发送到服务器

    ③ 编码类型为:application/x-www-form-urlencoded or multipart/form-data,请为二进制数据使用multipart编码

    ④ 没有历史记录

    ⑤ 参数类型没有限制,可以是字符串,也可以是二进制流

    ⑥ 数据不会显示在地址栏中,也不会缓存下来或保存在浏览记录中,所以POST请求比GET请求安全,但也不是最安全的方式。如需要传送敏感数据,请使用加密方式传输

    ⑦ 查询字符串不会显示在地址栏中

    ⑧ POST传输的数据量大,可以达到2M;而GET方法由于受到URL长度限制,只能传递大约1024字节

    ⑨ POST就是为了将数据传送到服务器端;而GET就是为了从服务器端获取数据

node.js基础

1.命令行窗口

(1)简介

  • 命令行窗口是在图形用户界面得到普及之前使用最为广泛的用户界面,它通常不支持鼠标,用户通过键盘输入指令,计算机接收到指令后,予以执行。它也被称为字符用户界面(CUI)、终端、CMD窗口、shell…。
  • 命令行窗口可以同时开启多个,并且它们之间是不会互相影响的。

(2)常用命令讲解

① dir:列出当前目录下面所有的文件。

② cd 目录名:进入到指定的目录,比如:cd Desktop(进入桌面)。.当前目录,…进入上级目录,比如:cd .(当前目录);cd …(上级目录)。

③ md 目录名:创建文件夹。

④ rd 目录名:删除文件夹。

⑤ cd.>文件名.后缀名:创建文件,比如:cd.>a.txt。

⑥ cls:清屏。

⑦ exit:退出命令行。

2.进程和线程

(1)进程

  • 概念:进程是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
  • 进程为程序的运行提供了必备的环境,所以代码和程序都是放在进程里面,它相当于工厂中的车间。

(2)线程

  • 概念:线程是计算机中最小的计算单位,它负责执行进程中的程序,相当于车间中的操作工人。
  • 分类:线程可以分为单线程(js就是单线程)和多线程(它可以根据任务决定开启几条线程)。
3.安装node.js
  • 直接去官网下载,安转过程中直接下一步。
  • 安装完成后,会自动配置好环境变量。配置好环境变量后,就可以在任何一个目录当中访问node命令(即配置成一个全局的环境变量)。如果系统没有自动配置环境变量,那么就要手动进行配置,配置过程:右击我的电脑——>点击属性——>点击高级系统配置——>点击高级,然后点击环境变量——>在系统变量中打开Path,并新建一条记录(node安装的位置,比如:c:\Program Files\node.js\)。
  • 在控制台通过node -v可以查看node 版本。
4.node如何运行js程序
  • 可以在命令行窗口运行js程序(包括:直接在命令行窗口书写js代码然后运行或者在命令行窗口通过node xxx.js运行js文件);也可以在编辑器上运行js程序,如:VSCode、WebStorm等。
5.Node的模块化开发思想
  • 在ES6之前,ECMAScript存在以下几个问题:

    (1)没有模块系统

    (2)官方标准库少/标准接口少

    (3)缺乏包管理系统

  • 模块化:如果程序设计的规模达到了一定的程度,则必须对其使用模块化。

    (1)前端只是进行页面的渲染,数据的请求及传输等,所以不太需要模块化

    (2)服务器端端开发,如果没有模块化思想就很难进行开发

    (3)模块化可以有多种形式,但都提供了能够将代码分隔为多个源文件的机制。如:Common.js

  • CommonJS规范:

    (1)CommonJS规范等提出,主要是为了弥补JavaScript没有模块化标准的缺陷。

    (2)CommonJS对模块的定义:

    ① 模块引用:require(“路径”);

    ② 模块定义

    ③ 模块标识

  • 总结:

    (1)从文件的角度看,每个JS文件就是一个模块;从结构看,多个JS文件之间可以相互require,共同实现一个功能,这整体上也是一个模块。

    (2)在Node.js中,一个模块中定义的变量、函数等,都只能在这个文件等内部有效;当需要从此JS文件的外部引用这些变量、函数时,就必须使用exports进行暴露,使用者通过require进行引用。

  • 实践1:

//one.js
let str = "Hello!";
let test = () => {
  console.log("今天你跑步了吗?");
}
exports.str = str;
exports.test = test;		//不能加小括号
//two.js
let fn = require('./one.js');		//必须添加.或者..,具体看引用的文件的位置
console.log(fn.str);
fn.test();

(1)如果一个js(one.js)文件引用了其他js(other.js)文件,然后另一个js(two.js)文件又引用了该js(one.js)文件,那么该js(two.js)文件不仅可以使用所引用的js(one.js)文件,还可以使用所引用的js(one.js)文件中引用的js(other.js)文件。

(2)在Node中,一个js文件就是一个模块。

(3)在Node中,通过require()函数来引入外部的模块。注意,引入外部模块时,其路径要加上.或者…。比如:当前目录就是./xxx;上一级目录就是./…/xxx。

(4)在Node中,每一个js文件中的js代码都是独立运行在一个小闭包中,而不是全局作用域,所以一个模块中的变量和函数在其他模块中无法访问。它的目的就是:使全局变量私有化,避免全局污染。

(5)在Node中,通过exports暴露模块中的变量和函数时,只需要将需要暴露给外部的变量或方法设置为exports的属性即可。

(6)练习:封装一个myFunc(能够进行求和、求平均数)。

//myFunc.js
exports.sum = (...numbers) => {
  let result = 0;
  numbers.forEach((item) => {
    result += item;
  });
    return result;
};

exports.avg = (...numbers) => {
  let result = 0;
  numbers.forEach((item) => {
    result += item;
  });
    return result / numbers.length;
};
//one.js
//文件模块
let func = require("./js/myFunc");		//路径可以带后缀名,也可以不带后缀名,系统都会自动补充完整
console.log(func);								//其实,这个func就是exports,所以输出结果为:{sum:[Function], avg:[Function]}
console.log(func.sum(1,2,3,4));		//输出结果为:10
console.log(func.avg(1,2,3,4));		//输出结果为:2.5

//核心模块
let http = require("http");

  • 实践2:

(1)模块标识

① 当使用require()引入外部模块的时候,使用的就是模块标识。通过模块标识可以找到指定的模块。

② 模块的分类:

内建模块:底层由C++编写,所以底层的模块都是内建模块。

文件模块:由用户自己创建的模块。其标识是:文件的路径(绝对路径、相对路径)。

核心模块:由node引擎提供的模块以及由node_modules提供的模块。其标识是:模块的名字,比如:http、fs、global…。

(2)exports和require的由来

  • Node中的全局对象不是window,而是global,它的作用类似window。

    console.log(window.exports);		//报错
    console.log(global.exports);		//undefined
    
  • exports和require不是全局变量,而是函数参数。(Node会在每个js文件的外面套上一个闭包函数)

  • 函数的标识:arguments(作用:获取函数的所有实参,是一个伪数组);

    arguments.callee(作用:返回函数本身)

    console.log(arguments.callee);		//输出结果为:[Function],如果想要看函数里面的内容,可以把它转换为字符串(通过:arguments.callee + "")
    
    

(3)node文件组成剖析

① 当node在执行模块中的代码时,它会首先在代码的最顶部添加如下代码:

function (exports, require, module, __filename, __dirname){

② 在代码的最底部,添加}

③ 所以模块中的代码都是包装在一个函数中执行的,并且在函数执行的同时传递了5个实参。

exports:该对象用来将函数内部的局部变量或局部函数暴露到外部。(函数的执行顺序是从上往下执行,所以一个模块中如果重复暴露同一个变量多次,那么以最后一次暴露的变量值为准)

require:用来引入外部的模块。

module:代表的是当前模块本身(module是一个对象)。exports就是module的属性,所以既可以使用exports导出,也可以使用module.exports导出。

console.log(exports);						//输出结果为:{}
console.log(module.exports);		//输出结果为:{}
//没有暴露或暴露错误或没有引入外部模块或引入错误等,都是直接在控制台中打印一个空对象,不会报错。

__filename:当前模块的完整路径(精确到当前文件名),如C:\Users\bin\Desktop\test\one.js。

__dirname:当前模块所在文件夹的完整路径(只精确到当前文件所在的文件夹名),如:C:\Users\bin\Desktop\test。

6.exports和module.exports的区别
  • exports只能使用.语法来向外暴露内部的变量,如:exports.xxx = xxx;。

  • module.exports既可以通过.语法,也可以直接赋值一个对象,如:module.exports.xxx = xxx; module.exports = {xxx:yyy};。

  • 具体原因可以参考值类型和引用类型的区别。

let md = new Object();
md.exp = new Objext({name:'andy'});
let exp = md.exp;		//这就是exports和module.exports建立的联系。如果此时给exports赋值一个对象,那么这种联系就会断开。所以,为了保持它们之间的这种联系,不能给exports赋值一个对象。
exp.name = "Mary";
console.log(exp.name);		//输出结果为:Mary
console.log(md.exp.name);	//输出结果为:Mary

//Person.js
function Person(name,age,sex){
  this.name = name;
  this,age = age;
  this.sex = sex;
}
Person.prototype = {
  play: function(){
    console.log(this.name + '去打篮球了!');
  }
};

//因为构造函数或类都是对象(JS中万物皆对象),所以这里只能用module.exports将该类导出。
module.exports = Person;
//one.js
let Person = require('./Person');
let p = new Person("andy",22,"男");
console.log(p);		//输出结果为:{name:'andy', age:22,sex:'男'}
7.包和包管理器

(1)包

  • 概念:ConmonJS的包规范允许我们将一组相关的模块组合到一起,形成一组完整的工具,这组工具就叫做包。

  • 组成:

    ① 包结构。它用于组织包中的各种文件,实际上就是一个压缩文件,解压以后还原为目录,该目录包括:package.json(描述文件)、bin(可执行二进制文件)、lib(js代码)、doc(文档)、test(单元测试)。

    ② 包描述文件-package.json。它描述了包的相关信息,以供外部读取分析。同时,它是一个JSON格式的文件,位于包的根目录下。

  • 注意:package.json文件中,不能加入任何注释。

(2)包管理器(NPM)

  • 概念:NPM,即Node Package Manager,它的作用相当于360管家。对于Node而言,NPM帮助其完成了第三方模块的发布、安装和依赖等。借助NPM,Node与第三方模块之间形成了很好的一个生态系统。

  • 常用命令:

    ① npm -v:查看版本

    ② npm version:查看所有模块的版本

    ③ npm search 包名/部分包名:搜索包

    ④ npm init:初始化package.json文件

    ⑤ npm install/i 包名:安装包

    ⑥ npm remove/r 包名:删除包

    ⑦ npm install/i 包名 --save:安装包并添加到依赖中

    ⑧ npm install:根据package.json下载当前项目所依赖的包

    ⑨ npm install 包名 -g:全局安装包,用于一些编译根据,比如:gulp、webpack等

注意:如果在页面中引入node_modules中某个模块,优先从当前目录引入,如果没有,则往上级查找,直到根目录。

(3)CNPM

  • 出现的意义:因为npm安装插件是从国外服务器下载,受网络影响大,可能出现异常,也有可能被墙。所以,淘宝团队就在中国做了一个NPM的镜像服务器(CNPM)。

  • CNPM是一个完整的npmjs镜像,可以用它代替官方版本(只读),它的同步频率目前为10分钟一次,以此保证尽量与官方服务同步。

  • 联系图:

  • 使用方式:参考https://npm.taobao

    主要是先通过命令($ npm install -g cnpm --registry=https://registry.npm.taobao)安装CNPM,然后就可以直接使用,使用方式跟NPM一样。

node.js基础-文件系统

1.Buffer(缓冲区)
  • 为什么要用Buffer?

    (1)在Node、ES6出现之前,前端工程师只需要进行一些简单的字符串或DOM操作就可以满足业务需要,所以对二进制数据是比较陌生的。

    (2)但在Node出现之后,前端工程师面对的技术场景发生了变化,可以深入到网络传输、文件操作、图片处理等领域,而这些操作都与二进制数据紧密相关。

    (3)Node里面的buffer,是一个二进制数据容器,数据结构类似于数组,专门用于Node中数据的存放。

  • Buffer的基本使用

    (1)历史使用方式:

    cosnt buf1 = new Buffer(10);		//10表示Buffer的长度
    console.log(buf1);			//输出结果为:<Buffer 00 00 00 00 00 00 00 00 00 00>
    

    这种方式在分配的内存中可能还存储着旧数据,所以存在安全隐患。(现在已经被废弃了)

    (2)新使用方式:

    Buffer提供了Buffer.from、Buffer.alloc、Buffer.allocUnsafe、Buffer.allocUnsafeSlow四个方法来申请内存。

    ① Buffer.from(string[,encoding]):将一个字符串转换为buffer(二进制)。其中,string是要编码的字符串;encoding是string的字符编码,默认为:‘utf-8’。

    let str = "www.baidu";		//一个字母在Buffer中占一个字节,比如:w在Buffer中就变成77
    let buffer = Buffer.from(str);
    console.log(buffer);
    console.log(buffer.toString());		//转换为十进制,又变成www.baidu
    console.log(str.length);		//输出结果为:13
    console.log(buffer.length);	//输出结果为:13
    
    let str1 = "你好"let buffer = Buffer.from(str1);
    console.log(str.length);		//输出结果为:2
    console.log(buffer.length);	//输出结果为:6
    //一个汉字在Buffer中占3个字节
    

    ② Buffer.alloc(size[,fill[,encoding]]):创建一个指定大小的Buffer。其中,size是新建的Buffer期望的长度,确定之后不能动态的改变;fill是用来预填充新建的Buffer的值,可以是string、Buffer、integer,默认为:0;encoding意思是:如果fill是字符串,则该值是它的字符编码,默认为:‘utf-8’。

    let buffer = Buffer.alloc(10);
    console.log(buffer);				//输出结果为:<Buffer 00 00 00 00 00 00 00 00 00 00>
    buffer[0] = 10;		//跟数组一样,也可以通过下标获取或赋值
    console.log(buffer);				//输出结果为:<Buffer 0a 00 00 00 00 00 00 00 00 00>
    buffer[1] = 0xfc;	//直接传入一个十六进制,输出就不会进行转换,因为本来输出就是十六进制
    console.log(buffer);				//输出结果为:<Buffer 0a fc 00 00 00 00 00 00 00 00>
    buffer[11] = 20;	//赋值操作超过Buffer的长度,所以不会进行处理
    console.log(buffer);				//输出结果为:<Buffer 0a fc 00 00 00 00 00 00 00 00>
    //Buffer也能够跟数组一样使用forEach进行遍历处理
    buffer.forEach((item,index) => {
      console.log(index +":" + item);
    });
    

    ③ Buffer.allocUnsafe(size):创建一个指定大小的Buffer,但是可能包含敏感数据。

    注意:Buffer是一个核心模块,但它不需要引入,可以直接使用。

    (3)Buffer使用的相关注意点:

    ① Buffer的结构和数组很像,操作的方法也和数组类似。

    ② Buffer中是以二进制的方式存储数据的。

    ③ Buffer是Node自带的,不需要引入,直接使用即可。

2.文件系统(File System)

(1)基本概念

① 在Node中,与文件系统的交互是非常重要的,服务器的本质就是将本地的文件发送给远程的客户端。

② Node通过fs模块与文件系统进行交互,该模块提供了一些标准文件访问API来打开、读取、写入文件,以及与其交互。

③ 使用fs模块之前,要先从核心模块中加载:const fs = require(“fs”);。

//同步文件写入:
//引入模块
let fs = require("fs");
//打开文件
//打开文件的两个类方法:异步:fs.open(path,flags[,mode],callback)、同步:fs.openSync(path,flags[,mode])。其中,flags是一个标识,比如:r就是读;w就是写。
let fd = fs.openSync("one.txt","w");
//写入内容
//写入文件的两个类方法:异步:fs.writeFile(file,data[,options],callback)、同步:fs.writeFileSync(file,data[,options])。其中,file是文件名;data是传入的数据;options包括encoding、mode、flag(默认是:'w'),它一般都是默认值就好。
fs.writeFileSync(fd,"Hello World!");
//保存并退出
//退出有两个类方法:异步:fs.close(fd,callback)、同步:fs.closeSync(fd)
fs.closeSync(fd);


(2)使用特点

① fs模块中所有的操作都有两种形式可供选择(同步和异步)。

② 同步文件系统会阻塞程序的执行,也就是除非操作完毕,否则不会向下执行代码。

③ 异步文件系统不会阻塞程序的执行,而是在操作完成时,通过回调函数将结果返回。

(3)文件操作

① 打开文件:fs.open(path,flags[,mode],callback)、fs.openSync(path,flags[,mode])。其中,callback有两个参数,分别是err和fd,fd代表读写的那个文件。

flags取值说明
r读取文件,文件不存在则出现异常
r+读写文件,文件不存在则出现异常
rs在同步模式下打开文件用于读取
rs+在同步模式下打开文件用于读写
w打开文件用于写操作,如果不存在则创建,如果存在则截断
wx打开文件用于写操作,如果存在则打开失败
w+打开文件用于读写,如果不存在则创建,如果存在则截断
wx+打开文件用于读写,如果存在则打开失败
a打开文件用于追加,如果不存在则创建
ax打开文件用于追加,如果路径存在则失败
a+打开文件用于读取和追加,如果不存在则创建该文件
ax+打开文件进行读取和追加,如果路径存在则失败

② 关闭文件

③ 写入文件操作

④ 读取文件操作

//异步文件写入:
//引入模块
let fs = reqire("fs");
//打开文件
fs.open("one.txt","w",(err,fd) => {
  //判断是否出错
  if(!err){
    //写入文件
    //fs.writeFile(file,data[,options],callback)中的callback有一个形参err
    fs.writeFile(fd,"你好!",(err) => {
      //写入成功
      if(!err){
        console.log("写入文件成功");
      }else{
        throw err;
      }
      //关闭文件
      //fs.close(fd,callback)中的callback有一个形参err
      fs.close(fd,(err) => {
        if(!err){
          console.log("文件已经保存并关闭!");
        }
      })
    });
  }else{
    throw err;
  }
})
//异步都是回调嵌套回调
//异步没有返回结果,结果都是通过回调函数返回
//异步是node的一个特色,它不会阻塞线程。如果一个线程发生了错误,后面的程序也会照样执行
//文件流式写入
//引入模块
let fs = require("fs");
//创建写入流
//fs.createWriteStream(path[,options]),该方法是同步的。其中,options是一个对象,包括flags、encoding、fd、mode、autoClose和start。
let ws = fs.createWriteStream("one.txt");
//打开通道
//通过“close”事件或“open”事件关闭或打开通道
ws.once("open", () => {
  console.log("通道已经打开!");
});
ws.once("close", () => {
  console.log("通道已经关闭!");
});
//写入内容
ws.write("Good Morning!");
ws.write("Good Afternoon!");
ws.write("Good Evening!");
//关闭通道
ws.end();

//读取文件:
//引入模块
let fs = require("fs");
//举例1:读取文本文件
//fs.readFile(path[,options],callback),其中,options是一个对象,包括encoding(默认为:null)和flag(默认为:r)两个属性;callback有两个形参,分别是err和data(string|Buffer)。
fs.readFile("one.txt",(err,data) => {
  //判断
  if(!err){
    console.log(data);									//二进制
    console.log(data.toString());				//十进制
  }else{
    throw err;
  }
});


//举例2:读取并写一张图片
fs.readFile("C:/Users/bin/Desktop/bg.jpg",(err,data) => {		//路径也可以写成:C:\\Users\\bin\\Desktop\\bg.jpg
  //判断
  if(!err){
    //写入图片文件
    fs.writeFile("img.jpg",data,(err) => {
      if(!err)
        console.log("写入成功");
    })else{
      throw err;
    }
  }else{
    throw err;
  }
})


//举例3:读取并写一个视频文件
fs.readFile("C:/Users/bin/Desktop/animate.mp4",(err,data) => {		//路径也可以写成:C:\\Users\\bin\\Desktop\\animate.mp4
  //判断
  if(!err){
    //写入视频文件
    fs.writeFile("animate.mp4",data,(err) => {
      if(!err)
        console.log("写入成功");
    })else{
      throw err;
    }
  }else{
    throw err;
  }
})

//one.txt
Good Morning!
//文件流式读取
//方法1:
//引入模块
let fs = require("fs");
//创建读取流和写入流
let rs = fs.createReafStream("C:/Users/bin/Desktop/animate.mp4");
let ws = fs.createWriteStream("animate.mp4");
//监听流的打开和关闭
rs.once("open",() => {
  console.log("读入通道已经打开!");
});
rs.once("close", () => {
  console.log("读入通道已经关闭!");
});
ws.once("open",() => {
  console.log("写出通道已经打开!");
});
ws.once("close", () => {
  console.log("写出通道已经关闭!");
});
//绑定data
rs.on("data",(data) => {
  //console.log(data);
  ws.write(data);
})

//方法2:
//引入模块
let fs = require("fs");
//创建读取流和写入流
let rs = fs.createReafStream("C:/Users/bin/Desktop/animate.mp4");
let ws = fs.createWriteStream("animate.mp4");
//创建管道
rs.pipe(ws);		//该方法是把方法1中的代码做了一个封装

Node基础-MongoDB

1.数据库(Database)

(1)基本概念

  • 数据库就是按照一定的数据结构来组织、存储和管理数据的仓库。

  • 通常情况下,程序都是在内存中运行的,一旦程序运行结束或者计算机断电,程序运行中的数据都会全部丢失。所以,我们需要将一些程序运行的数据持久化到硬盘之中,以确保数据的安全性。

  • 一般情况下,把数据保存到硬盘中,主要有以下两种方式:数据库和文件。其中,数据库是大批量数据持久化的普遍选择。

  • 普遍都采用数据库存储数据的原因:

    ① 数据库是有结构的,数据与数据之间可以建立各种关系,类似网状拓扑图。(方便查找)

    ② 数据库能够提供各种接口,让数据的处理(增删改查操作)变得快捷简单。

    ③ 给各种语言(PHP、jsp、…)提供了完善的接口。

(2)数据库的分类

  • RDBMS(关系型数据库):该数据库是通过一张张表来建立关联的,并且基本上都是使用SQL语言(结构化查询语言)来管理数据库的。例子:MySQL、SQL Server、Oracle、DB2…。

  • NoSQL(非关系型数据库):

    ① 该数据库没有行和列的概念,集合相当于“表”,文档相当于“行”,并且是用JSON来存储数据。(它与关系数据库是相辅相成的,不是哪个要取代哪个)

    ② 分类:键值存储数据库、列存储数据库、文档型数据库、图形数据库。

    ③ 举例:MongoDB、CouchDB、HBase、Redis…。

(3)关系型数据库和非关系型数据库的区分:

① 关系型数据库比较结构化,适合大型数据存储,但操作不是很灵活;非关系型数据库操作灵活,但不适合大型数据存储,比较适合微架构。两者是相辅相成的关系。

② 非关系型数据库适合的场景:

  • 数据模型比较简单
  • 需要灵活性更强的后台系统
  • 对数据库性能要求较高
  • 不需要高度的数据一致性
  • 非关系型数据库更适合小型、微型的数据存储;大型、稳定型的数据存储使用关系型数据库更合适。

③SQL和NoSQL的对比图:

2.MongoDB

(1)简介

  • MongoDB是为快速开发互联网Web应用而设计的数据库系统。
  • MongoDB的设计目标是极简、灵活,经常在Web应用栈的业务层被运用。
  • MongoDb是数据模型是面向文档的(传统的数据库是面向表单的),类似于JSON的结构,MongoDB这个数据库中存的是各种各样的BSON。

(2)安装

① 下载MongoDB。

  • Windows用户可以从https://www.mongodb/dl/win32/中下载,或者从官网https://www.mongodb/download-center/community中下载。(Mac用户可以从https://www.mongodb/download-center#community中下载)
  • MongoDB的版本中,偶数版本为稳定版(可以体验到最新的变化),奇数版本为开发版。
  • MongoDB对于32位系统支持不佳,使用3.2版本以后没有再对32位系统的支持。

② 步骤:

  • 安装MongoDB的数据库服务器。

  • 配置环境变量。(步骤:右击我的电脑——>选择属性——>选择“高级系统设置”——>点击“高级”,并选择环境变量——>在系统变量中,选择“Path”,然后点击“编辑”——>点击“新建“——>输入MongoDB的安装地址(一定要进到bin目录中,然后复制该地址)——>点击“确定”,完成)

  • 在C盘根目录创建一个文件夹data,在data文件夹中创建一个文件夹db。

  • 打开命令行窗口,直接输入mongod,启动服务器。(32位系统第一次启动时,要输入mongod --storageEngine=mmapv1)

  • 修改端口和路径的方法:mongod --dbpath 路径 --dbport 端口号,比如:mongod --dbpath C:\Users\bin\Desktop\data\db --dbport 123。

  • 将MongoDB设置为系统服务(好处:可以自动在后台启动,不需要每次都启动),设置步骤如下:

    在data文件夹中创建log文件夹,可以在命令行窗口通过:mkdir c:\data\log;

    创建配置文件mongod.cfg,并把它放置在MongoDB的安装位置上,具体位置参考C:\Program Files\MongoDB\Server\3.4。

    以管理员的身份打开命令行,运行以下代码:

    sc.exe ceate MongoDb binPath="\"C:\Program Files\MongoDb\Server\3.4\bin\mongod.exe\" --service --config=\"C:\Program Files\MongoDB\Server\3.4\mongod.cfg\""DisplayName="MongoDB" start="auto"
    //上面涉及到路径的代码,都以文件的实际路径为准(切记不要直接复制,如果路径不同的话)
    
    

    右击任务栏——>任务管理器——>服务——>启动MongoDB服务。

    如果服务器启动失败,证明上面的操作有误。此时,我们应该在控制台输入:sc delete MongoDB删除之前配置的服务,然后重新再来。

注意:
1、命令行下运行 MongoDB 服务器(命令:mongod) 配置 MongoDB 服务后,启动该服务(命令:net start MongoDB或者手动在任务管理器中启动) 任选一个方式启动就可以。
2、MongoDB的安装步骤和使用方法,还可以参照https://www.runoob/mongodb/mongodb-tutorial.html
3、MongoDB包括社区版和企业版,但不止如此,MongoDB公司还有MongoDB Atlas:Database as a Service。它的使用方法可以参照https://blog.csdn/duninet/article/details/103655622。

(3)使用

① MongoDB的基本组成:

  • 数据库(database):数据库是一个仓库,在仓库中可以存放集合。
  • 集合(collection):集合类似于数组,在集合中可以存放文档。
  • 文档(ducument):文档是数据库中的最小单位,我们存储和操作的内容都是文档。
  • 示意图:

② MongoDB的基本指令:

  • mongo:进入到MongoDB的后台。
  • show dbs:显示当前所有的数据库。
  • use 数据库名:进入或创建指定的数据库。
  • db:显示当前所在的数据库。
  • show collection:显示数据库中的所有集合。

③ 命令行进行CRUD(增删改查):

  • 往数据库中插入文档:db.< collection >.insert(doc),比如:db.student.insert({id:“001”, name:“andy”,age:22,sex:“男”});。

  • 查询当前集合中所有的文档:db.< collection >.find();。

  • 其他操作可以参考:https://www.mongodb

④ 可视化操作软件:mongodbmanagerpro_inst.exe

//插入数据:db.collection.insertOne()、db.collection.insertMany、db.collection.insert()
db.student.insert({name:"andy", age:22, sex:"男"});
db.student.insert([
  {name:"one", age:23, sex:"男",friend: "Lebron"},
  {name:"two", age:25, sex:"女"},
  {name:"three", age:27, sex:"男",friend: "Steven"}
]);
db.student.find();

//查询数据:db.collection.find({}),{}表示查询条件,如果要查询所有,可以只写{}或省略;db.collection.findOne()
db.student.find({sex:"男"});
db.student.findOne({sex:"男"});

//查询集合中文档的个数:
db.student.find().count();
db.student.find().length();

//更新数据:db.collection.update(<query>,<update>,<options>)、db.collection.updateOne()、db.collection.updataMany()、db.collection.replaceOne()。(updateOne只能更新匹配的第一条数据;而replaceOne可以更新匹配的任何一条数据)其中,query是查询条件,用{}包裹;update是要修改为什么数据,用{}包裹;option包括三个属性,分别是:upsert(可选,这个参数的意思是,如果不存在update的记录,是否插入objNew,true为插入,默认值是false,不插入)、multi(可选,mongodb默认值是false,只更新找到的第一条记录,如果这个参数为true,就把按条件查出来的多条记录全部更新)、writeConcern(可选,抛出异常的级别)。
//字段更新操作符:$set(设置某一个字段的值)、$unset(删除字段)
db.student.update({"name": "andy"},{$set:{age:18, hobby: "backetball"}});		//其中,如果修改的字段本来是没有的,那么就会自动添加这个字段,比如:hobby这个字段
db.student.update({"name": "one"},{$unset:{age:1}});		//删除name为one的这个文档的age这个字段的内容
db.student.updateMany({"sex": "男"},{$set:{age: 18}});		//这行代码与下一行代码是等价的
db.student.update({"sex": "男"},{$set:{age:18}},{multi: true});

//删除数据:db.collection.deleteMany()、db.collection.deleteOne()、db.colletion.remove(<query>,{justOne:<boolean>, writeConcern:<document>}),其中,query(可选)是删除的文档的条件;justOne(可选)如果设为true或1,则删除一个文档;writeConcern(可选)是抛出异常的级别。
db.student.remove({"sex": "女"});
db.student.remove({"sex": "男"},true);		//只删除一条数据
db.student.remove({});		//删除所有的数据,并且{}不能省略,跟find()不一样

//实际开发中,如果数据库中的数据非常重要,那么一般不会直接通过db.collection.remove()直接删除,而是通过一个字段值的改变,让用户感觉数据已经被删除,具体做法如下:
db.student.insert([
  {name:"first", content: "Hello", isDel: 0},
  {name:"second", content: "Hi", isDel: 0},
  {name:"first", content: "Bye", isDel: 0}
]);
db.student.update({name: "first", content: "Hello"},{$set:{isDel: 1}});
db.student.find({name: "first", isDel: 0});








(4)强化练习

//1.创建并进入test数据库
use test;
db;
//2.向数据库的colleges集合中插入六个文档(HTml5、Java、Python、区块链、K12、<PHP, "世界上最好的编程语言">
db.colleges.insert([
  {name: "HTML"},
  {name: "Java"},
  {name: "Python"},
  {name: "区块链"},
  {name: "K12"},
  {name: "PHP", intro: "世界上最好的编程语言"}
]);
//3.查询colleges集合中的文档
db.colleges.find();
//4.向数据库的colleges集合中插入一个文档(Golang)
db.colleges.insert({name: "Golang"});
//5.统计数据库colleges集合中的文档数量
db.colleges.find().count();
//6.查询数据库colleges集合中name为HTML5的文档
db.colleges.find({name: "HTML5"});
//7.向数据库colleges集合中的name为HTML5的文档,添加一个intro属性,属性值为“全栈基础!”
db.colleges.update({name: "HTML5"},{$set:{intro: "全栈基础!"}});
//8.使用{name: "大数据"}替换name为"K12"的文档
db.colleges.update({name: "K12"},{$set:{name: "大数据"}});
//9.删除name为PHP的文档的intro属性
db.colleges.update({name: "PHP"},{$unset:{intro: 1}});
//10.向name为HTML5的文档中,添加一个classes:{base:["h5+c3","js","jQuery","abc"], core:["三大框架","node.js"]}
db.colleges.update({name: "HTML5"}, {$set:{classes:{base:["h5+c3","js","jQuery","abc"], core:["三大框架","node.js"]}}});
//11.查询有核心课程为三大框架的文档
db.colleges.find("classes.core": "三大框架");
//12.向name为HTML5的文档中,添加一个新的核心课程"微信小程序"
//数组更新操作符:$addToSet(加一个值到数组内,而且只有当这个值不在数组内才增加)、$push(把value追加到field里)
db.colleges.update({name: "HTML5"},{$push:{"classes.core": "微信小程序"}});
db.colleges.update({name: "HTML5"},{$addToSet:{"classes.core": "微信小程序"}});		//没有效果,因为微信小程序已经存在
//13.向name为HTML5的文档中,删除基础课程"abc"
//数组更新操作符:$pop(删除数组内的一个值)
db.colleges.update({name: "HTML5"}, {$pop:{"classes.base": "abc"}});
//14.删除colleges集合
//方法(1)
db.colleges.remove({});
//方法(2)
db.colleges.drop();
//删除数据库:db.dropDatabase();

//15.向集合demos中插入10000个文档
var arr = [];
for(var i =0; i < 10000; i++){
	arr.push({counter: i});
}
db.demos.insert(arr);
db.demos.find();
//16.查询demos中counter为666的文档
db.demos.find({counter: 666});
//17.查询demos中的counter小于666的文档
//比较查询操作符:$lte(小于等于)、$lt(小于)、$gte(大于等于)、$gt(大于)
db.demos.find({counter: {$lt: 666}});
//18.查询demos中counter大于666的文档
de.demos.find({counter: {$gt: 666}});
//19.查询demos中counter大于66,小于666的文档
db.demos.find({counter:{$gt: 66, $lt: 666}});
//20.查询demos集合中的前10条数据
db.demos.find({counter: {$lt: 10}});
db.demos.find().limit(10);
db.demos.find().skip(0).limit(10);
//21.查看demos集合中的第11条到20条数据
db.demos.find().skip(11).limit(10);

//school.json里面的数据举例:[{cno:1001,cname:"HTML学院"},{con:1002,cname:"Python学院"},{cno:1003,cname:"Java学院"},{cno:1004,cname:"Go学院"}]
//section.json里面的数据举例:[{name:1,job:"辅导员",wages:10000,cno:1001,bonus:1688},{name:2,job:"讲师",wages:20000,cno:1001,bonus:2600},{name:3,job:"辅导员",wages:12000,cno:1001},{name:4,job:"辅导员",wages:12000,cno:1002,bonus:1688},{name:5,job:"讲师",wages:13000,cno:1002,bonus:1288},{name:6,job:"辅导员",wages:11000,cno:1003,bonus:2688},{name:7,job:"班主任",wages:9000,cno:1003},{name:8,job:"辅导员",wages:8000,cno:1002,bonus:1675},{name:9,job:"高级讲师",wages:30000,cno:1001,bonus:2345},{name:10,job:"辅导员",wages:17000,cno:1002,bonus:1345}]
//23.创建company数据库,将school和section集合导入到数据库中
db.school.find();
db.section.find();
//24.查询HTML5学院的所有老师
var cno = db.scholl.findOne({"cname":"HTML5学院"})o;
db.section.find({cno:cno});
//25.查询Java学院的所有员工
var cno = db.scholl.findOne({"cname":"Java"})o;
db.section.find({cno:cno});
//26.查询工资大于20000的员工
db.section.find({wages:{$gt: 20000}});
//27.查询工资在10000~20000之间的员工
db.section.find({wages:{$gte: 10000, $lte: 20000}});
//28.查询工资小于10000或大于25000的员工
db.section.find({$or:[{wages: {$lt: 10000}}, {wages: {$gt: 25000}}]});
//29.为所有薪资低于10000的员工增加工资1000元
//字段更新操作符:$inc(对一个数字字段的某个field增加value)
db.section.updateMany({wages:{$lte: 10000}}, {$inc: {wages: 1000}});









3.集合间关系

(1)一对一:在MongoDB中,可以通过内嵌文档的形式来体现出一对一的关系。(举例:身份证和人)

//一对一
举例1(内嵌文档形式):
db.onetoone.insert([
  {name:"one", wife:{name:"first", sex:"女"},sex:"男"},
  {name:"two", wife:{name:"second", sex:"女"},sex:"男"},
]);
db.onetoone.find();
//举例2(集合形式)

(2)一对多/多对一:可以通过多文档关联,包括一对一和一对多。(举例:父母和孩子、微博和评论)

//一对多(包括一对一):
db.weibo.insert([
  {weibo: "Hello"},
  {weibo: "Hi"}
]);
dbments.insert([
  {
  	weibo_id: ObjectID("xxx"),		//第一条微博的ID
  	list:[
      "1",
      "2",
      "3"
    ]
  },
  {
  	weibo_id: ObjectID("xxx"),		//第二条微博的ID
  	list:[
      "(1)",
      "(2)",
      "(3)"
    ]
  },
]);
//查看某个微博有哪些评论
var weibo_id = db.weibo.findOne({"weibo": "Hello"})._id;
dbments.find({weibo_id:weibo_id});

(3)多对多:可以通过多文档关联。(举例:老师和学生)

//多对多
db.teachers.insert([
  {
  	name:"techer1",
  	s_id:[
      ObjectID("xxx"),
      ObjectID("xxx")
    ]
  },
  {
  	name:"techer1",
  	s_id:[
      ObjectID("xxx")
    ]
  },
  {
  	name:"techer1",
  	s_id:[
      ObjectID("xxx"),
      ObjectID("xxx"),
      ObjectID("xxx")
    ]
  },
]);
db.students.insert([
  {
  	name:"student1",
  	t_id:[
      ObjectID("xxx"),
      ObjectID("xxx")
    ]
  },
  {
  	name:"student1",
  	t_id:[
      ObjectID("xxx"),
      ObjectID("xxx"),
      ObjectID("xxx")
    ]
  },
]);
db.teachers.find();
db.students.find();
4.排序和索引

(1)排序

  • 查询文档是,默认情况是按照_id的值进行排列(升序)。
  • sort()可以用来指定文档的排序规则,sort()内部需要传递一个对象来指定文档的排序规则,其中,1表示升序,-1表示降序。
  • limit、skip、sort的顺序可以任意改变,运行时会自动调整。
db.collection.find().sort({wages: 1});		//数据先按照wages升序排列,如果wages的值相同,则会按照_id升序排列

(2)索引

  • 在部分需求中,有时候只需要一个文档中的部分数据,这时候就得通过映射。
  • 在查询时,可以用第二个参数来设置查询的结果投影。
db.collection.find({},{name: 1});		//会显示name字段和_id字段,因为默认情况下,如果_id没有设置为0,就都会显示
db.collection.find({},{name: 1, _id: 0});		//只会显示name字段

Node进阶-Mongoose

1.需求分析

(1)为什么要用Mongoose?

  • 之前我们都是通过命令行或者shell来完成对数据库的各种操作的,但在开发中的大部分时候,我们都需要通过程序来完成对数据库的操作。而Mongoose就是一个让我们可以通过Node来操作MongoDB的模块。

(2)Mongoose是什么?

  • Mongoose是一个对象文档模型(ODM)库,它对Node原生的MongoDB模块进行了进一步的优化封装,并提供了更多的功能。具体说明和使用可以参照官网:http://mongoosejs/

(3)Mongoose的优势

① 可以为文档创建一个模式结构(Schema)

② 可以对模型中的对象/文档进行验证

③ 数据可以通过类型转换为对象模型

④ 可以使用中间件来应用业务逻辑挂钩

⑤ 比Node原生的MongoDB驱动更容易

2.mongoose的基本使用

(1)相关概念

  • mongoose中提供了几个新的对象:

    ① Schema(模式对象):Schema对象定义约束了数据库中的文档结构。

    ② Model:Model对象作为集合中的所有文档的表示,相当于MongoDB数据库中的集合collection。

    ③ Document:Document表示集合中的具体文档。

  • 使用步骤

    ① 下载安装Mongoose。(npm i mongoose --save)

    ② 在项目中引入mongoose。(let mongoose = require(“mongoose”);

    ③ 连接MongoDB数据库。(mongoose.connet(‘mongodb://数据库的ip地址:端口号/数据库名’,其中,如果端口号是默认端口号—27017,则可以省略)

    ④ 监听MongoDB数据库的连接状态。在mongoose对象中,有一个属性叫做connection,该属性表示的就是数据库连接,通过监视该属性的状态,就可以监听到数据库的连接与断开。(mongoose.connection.once(“open”,function(){}); mongoose.connection.once(“close”, function(){}); )

    ⑤ 案例实操:

//1.在项目的根目录打开命令行窗口,通过命令(npm install mongoose --save)安装mongoose。
//2.连接数据库
//(1)引入模块
let mongoose = require("mongoose");
mongoose.connect('mongodb://localhost/m_data');			//其中,localhost后面是你自己要使用的数据库名
//(2)监听各种状态
let db = mongoose.connection;
db.on('error', () => {
  console.log("连接失败!");
});
db.once('open', funciton()[
  console.log("连接成功!");
]);
db.one('close', function(){
  console.log("数据库断开成功!");
});
//3.创建Schema(模式对象)
let Schema = mongoose.Schema;
let personSchame = new Schema({
  name: String,
  age: Number,
  sex: {
    type: String,
    default: "男"
  },
  chat:String
});
//4.创建Model对象
let personModel = mongoose.model("person", personSchema);		//集合的名字如果是单数,那么写进数据库的时候,系统会自动变成复数,比如:person会变成people
//5.插入文档
personModel.create({
  name: "andy",
  age: 22,
  chat: "bin"
}, (err) => {
  if(!err){
    console.log("插入成功");
  }else{
    throw err;
  }
});

3.mongoose对文档进行增删改查
//1.连接数据库
//(1)引入模块
let mongoose = require("mongoose");
mongoose.connect('mongodb://localhost/m_data');			//其中,localhost后面是你自己要使用的数据库名
//(2)监听各种状态
let db = mongoose.connection;
db.on('error', () => {
  console.log("连接失败!");
});
db.once('open', funciton()[
  console.log("连接成功!");
]);
db.one('close', function(){
  console.log("数据库断开成功!");
});
//2.创建Schema(模式对象)
let Schema = mongoose.Schema;
let personSchame = new Schema({
  name: String,
  age: Number,
  sex: {
    type: String,
    default: "男"
  },
  chat:String
});
//3.创建Model对象
let personModel = mongoose.model("person", personSchema);
//4.增删改查
//4.1增加
personModel.create([
  {name: "one", age: 21, chat:"A", sex:"女"},
  {name: "two", age: 23, chat:"B", sex:"男"},
  {name: "three", age: 25, chat:"C", sex:"男"},
  {name: "four", age: 28, chat:"D", sex:"女"}
], (err) => {
  if(!err){
    console.log("插入成功");
  }else{
    throw err;
  }
});
//4.2查找
personModel.find({}, (err,docs) => {	
  if(!err){
    console.log(docs);		//docs的类型是一个数组或对象
  }
});
//索引方式1:
personModel.find({},{name:1,_id:0},(err,docs) => {
  if(!err){
    console.log(docs);
  }else{
    throw err;
  }
});
//索引方式2:
personModel.find({},"-_id name sex",(err,docs) => {		//注意:1.只有_id能用符号代表不显示,其他字段都不能使用,其他字段如果不要显示,直接不写就可以了。2.每个字段名都以空格分隔。
  if(!err){
    console.log(docs);
  }else{
    throw err;
  }
});
//
personModel.find({},{name:1,_id:0}, {skip:2, limit:2} (err,docs) => {
  if(!err){
    console.log(docs);
  }else{
    throw err;
  }
});
//4.3修改:Model.update()、Model.updateOne()、Model.updateMany()
personModel.update({name:"three"},{$set:{age:18}},{multi:"true"},(err) = > {		//multi表示修改多条数据
  if(!err){
    console.log("修改成功");
  }else{
  throw err;
}
});
//4.4删除:Model.remove()、Model.deleteOne()、Model.deleteMany()
personModel.remove({name: "two"},(err) => {
  if(!err){
    console.log("删除成功");
  }else{
    throw err;
  }
});
//4.5统计文档的个数:Model.count()
personModel.count({}, (err.count) => {		//第一个参数一定是条件,空对象则表示统计该集合所有的文档个数
  if(!err){
    console.log(count);
  }
});
//5通过单独创建一个文档,然后添加该文档到集合collection中的方法增加
let person = new personModel({
  name:"five",
  age:26,
  chat:"F"
});
person.save((err,product) => {
  if(!err){
    console.log(product);
  }
});

Node进阶-常用模块

1.前言引入

(1)举例:

//例子:一个可以运行在网页上的Node页面
let http = require("http");
//创建服务器
let server = http.createServer((req,res) => {
  console.log(req.url);			//输入http://127.0.0.1/,打印的结果为:/;输入http://127.0.0.1/test,打印的结果为:/test。
  res.writeHead(200,{"Content-type":"text/html;charset=UTF-8"});
  res.write("<h1>你好!</h1>");
  res.write("Hello");
  res.end("World!");
});
//监听
server.listen(80,"127.0.0.1");

(2)Node.js没有web容器

  • Node没有根目录,不能像PHP、Javaweb一样,通过切换目录结构切换页面。所有的页面资源都得通过路径配置。
  • 在Node中,采用fs模块读入文件,并手动配置路由。
2.HTTP-路由(HTTP模块)
  • 演示1(访问html文件):
let http = require("http");
let fs = require("fs");
//创建服务器
let server = http.createServer((req,res) => {
  if(req.url === "/test1.html"){
   fs.readFile("./test1.html" (err,data) => {
     if(!err){
       es.writeHead(200,{"Content-type":"text/html;charset=UTF-8"});
   		 res.end(data)}
   }); 
  }else if(req.url === "/test1.html"){
   fs.readFile("./test1.html" (err,data) => {
     if(!err){
       es.writeHead(200,{"Content-type":"text/html;charset=UTF-8"});
   		 res.end(data)}
   });    
  }else if(req.url === "/"){
   es.writeHead(200,{"Content-type":"text/html;charset=UTF-8"});
   res.end("Hello World!")}else{
   res.writeHead(404,{"Content-type":"text/html;charset=UTF-8"});
   res.end("访问的页面不存在!")}
})//监听
server.listen(80,"127.0.0.1");
//test1.html
<body>
  <h1>
    HTML5
  </h1>
</body>
//test2.html
<body>
  <h1>
    Node.js
  </h1>
</body>
  • 演示2(访问CSS、image、source文件—有视频文件等):
let http = require("http");
let fs = require("fs");
//创建服务器
let server = http.createServer((req,res) => {
  if(req.url === "/test1.html"){
   fs.readFile("./test1.html" (err,data) => {
     if(!err){
       es.writeHead(200,{"Content-type":"text/html;charset=UTF-8"});
   		 res.end(data)}
   }); 
  }else if(req.url === "/test1.html"){
   fs.readFile("./test1.html" (err,data) => {
     if(!err){
       es.writeHead(200,{"Content-type":"text/html;charset=UTF-8"});
   		 res.end(data)}
   });    
  }else if(req.url === "/"){		//根目录
   es.writeHead(200,{"Content-type":"text/html;charset=UTF-8"});
   res.end("Hello World!")}else if(req.url === "/css/index.css"){			//css
   fs.readFile("./css/index.css" (err,data) => {
     if(!err){
       es.writeHead(200,{"Content-type":"text/css"});
   		 res.end(data)}
   });    
  }else if(req.url === "/img/xxx.jpg"){			//jpg
   fs.readFile("./img/xxx.jpg" (err,data) => {
     if(!err){
       es.writeHead(200,{"Content-type":"image/jpg"});
   		 res.end(data)}
   });    
  }else if(req.url === "/source/xxx.mp4"){			//mp4
   fs.readFile("./source/xxx.mp4" (err,data) => {
     if(!err){
       es.writeHead(200,{"Content-type":"video/mpeg4"});
   		 res.end(data)}
   });    
  }else{
   res.writeHead(404,{"Content-type":"text/html;charset=UTF-8"});
   res.end("访问的页面不存在!")}
})//监听
server.listen(80,"127.0.0.1");
//css/index.css
h1{
  background-color: red;
  font-size: 50px;
  coloe: green;
}
//test1.html
<head>
  <link rel="stylesheet" href="css/index.css">
</head>
<body>
  <h1>
    HTML5
  </h1>
  <video src="source/xxx.mp4" controls="controls"></video>
</body>
//test2.html
<body>
  <h1>
    Node.js
  </h1>
  <img src="img/xxx.jpg" alt="">
</body>
//img/xxx.jpg
//source/xxx.mp4

注意:在Node中,没有找不到页面,而有没有做特殊处理,那么始终都是显示根目录的内容。

(3)Node.js擅长于顶层路由设计,url与物理文件并不是一一对应的。

3.URL模块

(1)简介

  • 在开发过程中,通常需要获取URL地址中的某一部分。获取方法有以下两种:

    ① 正则表达式

    ② URL模块。URL模块提供了一些实用的函数,用于URL的处理和解析

  • 引入该模块的方法:

    const url = require("url");
    
  • 一个网址的组成:

  • URL模块的作用:可以把一个URL地址转换为一个URL对象。

  • 把URL地址转换为一个URL对象的方式:

    ① 利用WHATWG API解析一个URL字符串:

    const { URL } = require('url');
    const myURL = new URL('https://xxx');
    //现在用得比较多的方式
    //一个完整的URL地址,就一定要用到这个
    

    ② 通过Node.js提供的API解析一个URL:

    const url = require('url');
    const myURl = url.parse('https://xxx');
    //要截取http服务中的东西,推荐用这种方式
    
  • 用法举例:

let http = require("http");
let url = require("url");

http.createServer((req,res) => {
  //console.log(req.url);			//该属性获取的地址是从路径开始,并且哈希值(#)不能获取
  let myUrl = url.parse(req.url);		//得到的是一个URL对象
  //该地址不能用第一种转换方式(new URL('xxx')去解析,因为该地址不是一个完整的URL地址。
  console.log(myUrl);
  res.end("Hello World!");
}).listen(80,"127.0.0.1");
4.HTTP-表单提交(HTTP模块)
  • 案例巩固:

(1)get:

//get.html
<body>
  <form action="http://127.0.0.1:80" method="get">
    <p>
      姓名:<input type="text" name="name">
    </p>
    <p>
      年龄:<input type="text" name="age">
    </p>
    <p>
      <input type="radio" name="sex" value=""><input type="radio" name="sex" value=""></p>
    <input type="submit" value="提交">
  </form>
</body>
//one.js
let http = require("http");
let url = require("url");

http.createServer((req,res) => {
  let myUrl = url.parse(req.url,true);		//得到的是一个URL对象
  //url.parse()中的第二个参数,如果是true,就代表通过该对象的属性获取到的所有数据,都将以对象的形式展示。
  let queryObj = myUrl.query;		//queryObj本来是一个字符串,但因为url.parse()的第二个参数是true,所以变成了一个对象
  console.log(queryObj);				//输出结果为:{name: 'andy', age: '22', sex: '男'}
  console.log(queryObj.name);		//输出结果为:andy
  console.log(queryObj.age);		//输出结果为:22
  console.log(queryObj.sex);		///输出结果为:男
  res.end("Hello World!");
}).listen(80,"127.0.0.1");

(2)post:

//post.html
<body>
  <form action="http://127.0.0.1:80/postmsg" method="post">
    <p>
      姓名:<input type="text" name="name">
    </p>
    <p>
      年龄:<input type="text" name="age">
    </p>
    <p>
      性别:
      <input type="radio" name="sex" value=""><input type="radio" name="sex" value=""></p>
    <p>
      学科:
      <input type="checkbox" name="college" value="HTML5">HTML5
      <input type="checkbox" name="college" value="Python">Python
      <input type="checkbox" name="college" value="Java">Java
    </p>
    <p>
      照片:
      <input type="file" name="photo">
    </p>
    <input type="submit" value="提交">
  </form>
</body>
//two.js
let http = require("http");
let url = require("url");
let querystring. require("querystring");		//该模块是查询字符串,它里面的方法可以把一个类似'foo=bar&abc=xyz&abc=123'的字符串解析为一个对象{foor: 'bar', abc: ['xyz','123']}

http.createServer((req,res) => {
  if(req.url === "/postmsg" && req.method.toLowerCase() === "post"){
    //1.变量
    let allData = "";
    //2.接收小段数据
    req.on("data",(buf)=>{
      allData += buf;
    });
    //3.所有的数据传递完毕
    req.once("end",()=>{
      res.end("OK!");
      let dataObj = querystring.parse(allData);
      console.log(dataObj);			//输出结果为:{name:'andy',age:'22',sex:'男',college:['HTML5','Python'],photo:'1.jpg'}
      console.log(dataObj.name);
      consoel.log(dataObj.age);
    })
  }
}).listen(80,"127.0.0.1");


(3)表单提交之图片处理

① 利用node-formidable第三方框架,可以快速处理表单的数据,并把它们存放到本地的文件中。(利用npm -i -S formidable安装该框架)

//在根目录创建一个文件夹,文件夹名为:uploads
//two.js
let http = require("http");
let url = require("url");
let querystring. require("querystring");
let formidable = require("formidable");
let util = require("util");

http.createServer((req,res) => {
  if(req.url === "/postmsg" && req.method.toLowerCase() === "post"){
    //1.实例化对象
    let form = formidable.IncomingForm();
    //2.设置上传的文件路径
    form.uploadDir = "./uploads";
    //3.获取表单的内容
    form.parse(req,(err,fields,files) => {
      res.writeHead(200,{'content-type': 'text/plain;charset=UTF-8'});
      res.write('received upload:\n\n');
      res.end(util.inspect({fields:fields,files:files}));		//util模块的util.inspect()方法返回object的字符串表示
    });
  }
}).listen(80,"127.0.0.1");

② 利用uuid第三方框架,生成一个独一无二的标识。它支持1,3,4,5UUIDs,其中我们下面使用的1UUIDs是基于时间戳生成独一无二的标识。(利用npm install uuid安装该框架)

//在根目录创建一个文件夹,文件夹名为:uploads
//two.js
let http = require("http");
let url = require("url");
let querystring. require("querystring");
let formidable = require("formidable");
let util = require("util");
let uuidv1 = require("uuid/v1");
let path = require("path");
let fs = require("fs");

http.createServer((req,res) => {
  if(req.url === "/postmsg" && req.method.toLowerCase() === "post"){
    //1.实例化对象
    let form = formidable.IncomingForm();
    //2.设置上传的文件路径
    form.uploadDir = "./uploads";
    //3.获取表单的内容
    form.parse(req,(err,fields,files) => {
      //3.1 生成随机的名称
      //console.log(uuidv1());
      let name = uuidv1();
      
      //path模块的path.extname()方法返回path的扩展名,即从path的最后一部分中的最后一个.(句号)字符到字符串结束。如果path的最后一部分没有.或path的文件名的第一个字符串是.,则返回一个空字符串。比如:path.extname('index.')——返回的是:'.'
      //3.2 获取上传文件的后缀
      let extName = path.extname(files.photo.files);
      
      //3.3 设置路径
      let oldPath = __dirname + "/" + files.phpto.path;
      let newPath = __dirname + "/uploads/" + name + extName;
      
      //3.4 修改文件名
      //fs模块中的fs.rename(oldPat,newPath,callback)可以修改文件的名字
      fs.rename(oldPath,newPath,(err) => {
        if(!err){
          res.writeHead(200,{'content-type': 'text/html;charset=UTF-8'});
          res.write('写入成功!');
          res.end(util.inspect({fields:fields,files:files}));		//util模块的util.inspect()方法返回object的字符串表示
        }else{
          throw err;
        }
      });
      
    });
  }
}).listen(80,"127.0.0.1");

5.静态资源库
  • 目录结构:

  • 具体的实现代码:
let http = require("http");
let fs = require("fs");
let path = require("path");
let url = require("url");

//1.创建服务器
http.createServer((req,res) => {
  //2.获取url地址
  let pathUrl = url.parse(req.url);
  let pathName = pathUrl.pathname;
  //console.log(pathUrl);
  //console.log(pathName);
  
  //3.处理路径
  if(pathName.lastIndexOf(".") === -1){
    pathName += "/index.html";
  }
  
  //4.拼接路径
  let fileUrl = "./" + path.normalize("./static/" + pathName);		//注意:/和\\表示的内容是一样的
  //path模块中的path.normalize()方法会规范给定的path,并解析'..'和'.'片段。(当path是一个长度为零的字符串,则会返回'.',表示当前目录)
  
  //获取路径的后缀
  let extname = path.extname(fileUrl);
  
  //5.读取文件
  fs.readFile(fileUrl, (err,data) => {
    //5.1 没有找到
    if(err){
      res.writeHead(404,{"Content-Type":"text/html;charset=UTF-8"});
      res.end("<h1>404,当前的页面不存在!</h1>");
    }
    //mine类型就是content-type,在这里要引入mine.json文件
    //5.2 找到
    getContentType(extname, (contentType)=>{
      res.writeHead(20,{"Content-Type":contentType + ";charset=UTF-8"});
      red.end(data);
    });
  });
  
}).listen(80,"127.0.0.1");

function getContentType(extName, callback){
  //读取文件
  fs.readFile("./mine.json", (err,data) => {
    if(err){
      throw err;
      return;
    }
    
    console.log(data);		//输出的是Buffer二进制数据
    let mineJson = JSON.parse(data);
    console.log(mineJson);		//输出的是JSON对象
    let contentType = mineJson[extName] || "text/plain"; //JSON本身就是一个特殊的对象,所以可以根据键值对获取相对应的值
    callback(contentType);
  })
}

  • 作用:把一些静态的资源放在一个文件夹(静态资源库)中,使Node可以像其他后端语言(有web容器)一样,可以根据目录结构访问相对应的资源
6.模版引擎
  • Node中的模版引擎主要有两种,分别是:EJS和Jade。

  • 安装EJS的命令:npm i ejs --save。

  • EJS的具体使用可以参考:EJS官网

//model/data.json
{
  "lists":[
    {"name":"andy", "age":22},
    {"name":"Mary", "age":24},
    {"name":"Tim", "age":20}
  ],
  "title":"案例事件"
}
//view/index.ejs
<body>
  <div>
    <span>
    	<%= title%>
    </span>
  </div>
  <ul>
    <%
  		for(var i=0; i<lists.length; i++){
  	%>
    <li>
    	<div>
        <span style="background-color: <%= i > 1 ? "blue" : "red" %> "><%= (i+1)%></span>
      </div>
      <div>
        <%= lists[i].name%>
      </div>
      <div>
        <%= lists[i].age%>
      </div>
    </li>
    <%
    	}
    %>
  </ul>
</body>
//one.js
let http = require("http");			//https的端口号是443,http的端口号是80
let fs = require("fs");
let ejs = require("ejs");

http.createServer((req,res) => {
  //1.读取数据
  getDataJson((dataJson)=>{
    //2.读取模版
    fs.readFile("./view/index.ejs",(err,data) => {
      let ejsData = data.toString();
      
      //3.实例化模版
      let tmp = ejs.render(ejsData,dataJson);
      
      //4.返回页面
      res.writeHead(200,{"Content-Type": "text/html;charset=UTF-8"});
      res.end(tmp);
    })
  })
}).listen(80,"127.0.0.1");

const getDataJson = (callback) => {
  fs.readFile("./model/data.json",(err,data) => {
    if(!err){
      let jsonData = JSON.parse(data);
      callback(jsonData);
    }else{
      throw err;
    }
  });
}

Node进阶-Express

1.Express简介

(1)Express是什么?

  • Express框架是目前最流行的node.js后端框架之一,相当于jQuery和js之间的关系。
  • 但是Express与node.js和jQuery与js之间的关系又有所不同:jQuery会对原生JS中的特性进行二次封装;而Express不对Node.js已有的特性进行二次封装,只是在它之上扩展了Web应用所需的基本功能,所以原生Node中的方法在Express中都是可以使用的。
  • 功能类似的后端框架:Koa.js、egg.js、hapi.js。

(2)安装Express

  • 安装步骤:

① 进入应用目录

② 利用npm init命令为自己的应用创建一个package.json文件

③ 安装Express,并将其保存到依赖列表中。(npm install express --save)

④ 如果只是临时安装Express,不想将它添加到依赖列表中,只需略去–save参数即可。(npm install express)

2.Express-基本使用
  • 举例:
let express = require("express");
let app = express();

app.get("/",(req,res) => {
  console.log(req.url);		//输出结果为:/
  res.send("Hello World!");
});
app.get("/html5", (req,res) => {
  console.log(req.url);		//输出结果为:/html5
  res.send("HTML5");
});
app.get("/bin/:name", (req,res) => {
  console.log(req.params.name);		//输出结果为发送请求时传递的参数,比如:请求地址为:127.0.0.1:3000/bin/andy,那么输出结果就是andy
  res.send("fighting!");
});
app.listen(3000);

注意:这里所创建的是一个最最简单的Express应用,并且仅仅只有一个文件,它和通过Express应用生成器所创建的应用完全不一样。Express应用生成器所创建的应用框架包含多个JavaScript文件、Jade模版和针对不同用途的子目录。(所以,当做一些工程或者项目的时候,一般都是通过Express应用生成器来创建应用)

3.Express-搭建静态资源库
  • 举例:
//想要搭建的静态文件夹(静态资源库)命名为public
let express = require("express");
let app = express();

app.use(express.static("./public"));

app.get("/", (req,res) => {
  res.send("Hello World!");
});

app.listen(3000);
//Express搭建的静态资源库默认也会给文件夹的后面添加index.html,如果找不到则显示错误,找到则显示相关的页面
//静态文件夹设置在127.0.0.1:3000,所以通过127.0.0.1:3000就可以访问静态文件中的根目录的index.html,其他文件以此类推。(注意:通过127.0.0.1:3000访问,不能显示Hello World!页面)

  • 举例2:
let express = require("express");
let app = express();

app.use(express.static("./public"));

app.get("/test", (req,res) => {
  res.send("Hello World!");
});

app.listen(3000);
//通过127.0.0.1:3000/test访问,可以显示Hello World!页面。
//静态资源库还是设置在根目录下,所以通过127.0.0.1:3000还是可以访问静态文件中的根目录的index.html,其他文件也以此类推。
  • 举例3:
let express = require("express");
let app = express();

app.use("/static",express.static("./public"));		//第一个参数是设置静态资源库的路径,如果没有改参数,则默认设置在根目录下

app.get("/", (req,res) => {
  res.send("Hello World!");
});

app.listen(3000);
//通过127.0.0.1:3000访问,可以显示Hello World!页面。
//静态资源库是设置在127.0.0.1:3000/static路径下,所以通过127.0.0.1:3000/static可以访问静态文件中的根目录下的index.html,其他文件以此类推。

注意:主要调用多次app.use()方法,就可以配置多个静态资源库。

4.Express-路由使用
let express = require("express");
let app = express();

app.get("/", (req,res) => {
  res.send("Hello!");
  res.send({name:"andy", age:22});
  res.send("<input type='date'>");
  res.status(404).send("Bad Request");		//send()方法只能发送一次响应/请求,如果出现多个send()方法,则会出现错误
  
  //可以通过write()和end()方法实现发送多个响应
  res.write("Hello");
  res.write("World");
  res.end("!");		//如果最后没有写end(),则这个请求会一直处于挂起状态,所以最后一定要加上end()方法
});

app.get("/:name/:age", (req,res) => {
  //let name = req.params.name;
  let name = req.params["name"];
  let age = req.params.["age"];
  res.write(name);
  res.end(name);
});

app.listen(3000);
5.Express-模版引擎
  • 使用方法:
//使用ejs之前,先通过命令(npm install ejs --save)安装ejs
//one.js
let express = require("express");
let app = express();

//指定视图所在的位置
app.set('views','./views');		//在当前目录创建一个views的文件夹
//注册模版引擎
app.set('view engine', 'ejs');

app.get("/",(req,res) => {
  res.render("index",{"lists":["andy",22,"篮球"]});
});
app.listen(3000);
//所在位置:views/index.ejs
<body>
  <ul>
    <%
    	for(var i=0; i < lists.length; i++){
    %>
    <li><%= lists[i] %></li>
    <%
    	}
    %>
  </ul>
</body>
  • 对比上面用原生Node.js写过的模版引擎,用Express的ejs模版引擎的具体实现代码如下:
//one.js
let express = require("express");
let app = express();
let fs = require("fs");

//指定视图所在的位置
app.set('views','./views');		
//注册模版引擎
app.set('view engine', 'ejs');

app.get("/", (req,res) => {
  getDataJson((dataJson)=>{
    res.render("index",dataJson);
  });
});
app.listen(3000);

const getDataJson = (callback) => {
  fs.readFile("./model/data.json",(err,data) => {
    if(!err){
      let jsonData = JSON.parse(data);
      callback(jsonData);
    }else{
      throw err;
    }
  });
}
6.Express-应用生成器
  • 通过应用生成器工具,express可以快速创建一个应用的骨架。

  • 安装:npm install express-generator -g

  • 常用操作:

    (1)express -h:-h选项可以列出所有可用的命令选项。

    (2)npm install:安装所有的依赖包。

  • 设置视图模版:express --view=ejs demo1(其中,demo1是项目的名称)。

  • 启动应用:

    (1)MacOS或Linux平台:DEBUG=myapp npm start

    (2)Windows平台:set DEBUG=myapp & npm start

  • 启动应用之后,在浏览器中打开http://localhost:3000/网址,可以看到这个应用。

  • 创建过程:在想要放置项目的地方,输入cmd打开命令行窗口——>express --view=ejs demo1——>cd demo1——>npm install——>SET DEBUG=demo1:* & npm start

  • 通过Express应用生成器创建的应用一般都有如下目录结构:

  • app.js入口文件详解:
//引入包文件
let express = require('express');
let path = require('path');
let favicon = require('serve-favicon');
let logger = require('morgan');
let cookieParser = require('cookie-parser');
let bodyParser = require('body-parser');

//引入路由文件
let index = require('./routes/index');
let users = require('./routes/users');

//生成express实例
let app = express();

// 设置视图引擎
app.set('views', path.join(__dirname, 'views'));// 制定模板目录
app.set('view engine', 'ejs'); // 设置模板引擎为ejs

//使用包
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

//让用户访问路由
app.use('/', index);
app.use('/users', users);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  var err = new Error('Not Found');
  err.status = 404;
  next(err);
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};
  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

//向外暴露app实例
moule.exports = app;
7.Express-中间件
  • 中间件其实就是处理过程中的一个环节(本质上就是一个函数)。

  • 中间件是一个函数,它可以访问请求对象,响应对象,和web应用中处于请求-响应循环流畅中的中间件,一般被命名为next的变量。

  • 中间件的功能包括:

    (1)执行任何代码;

    (2)修改请求和响应对象;

    (3)终结请求-响应循环;

    (4)调用堆栈中的下一个中间件。

  • 中间件的分类:

    (1)应用级中间件

    (2)路由级中间件

    (3)错误处理中间件

    (4)内置中间件

    (5)第三方中间件

  • 中间件的挂载方式:use(应用级中间件)、路由方式(路由级中间件)。

  • 应用级中间件:

//先通过npm init -y初始化化项目
//然后安装Express框架
const express = require('express');
const app = express();
let total = 0;

app.use((req,res,next) => {		//第一个参数不写,代表浏览器发送任何请求都会触发该中间件
  console.log('被访问了!');
  next();		//next()方法的作用就是把请求传递到下一个中间件
});

app.use('/user',(req,res,next) => {
  //记录访问时间
  console.log(Data.now());
  next();
});

app.use('/user',(req,res,next) => {
  //记录访问日志
  console.log('访问了/user');
  next();
});

app.use('/user',(req,res) => {
  total++;
  console.log(total);
  res.send('result');
});

app.listen(3000,() => {
  console.log('running...');
});
  • 路由级中间件(get、post、put、delete):
const express = require('express');
const app = express();

app.get('/abc', (req,res.next) => {	//这里实际上是一个路由,但包含多个中间件
  console.log(1);
  next();		//后端控制台依次输出:1,2;前端页面显示:abc
  //如果没有next()这行代码,那后端控制台只会输出:1;前端页面会一直处于请求状态
},(req,res) => {
  console.log(2);
  res.send('abc');
});

app.listen(3000, () => {
  console.log('running...');
});
const express = require('express');
const app = express();

app.get('/abc', (req,res.next) => {	//这里实际上是一个路由,但包含多个中间件
  console.log(1);
  next('route');		//跳转到下一个路由		
  //后台控制台依次输出:1,3;前端页面显示:Hello
},(req,res) => {
  console.log(2);
  res.send('abc');
});

app.get('/abc',(req,res) => {
  console.log(3);
  res.send('Hello');
});

app.listen(3000, () => {
  console.log('running...');
});
const express = require('express');
const app = express();

var cb0 = function(req,res,next) {
  console.log('CB0');
  next();
}

var cb1 = function(req,res,next) {
  console.log('CB1');
  next();
}

var cb2 = function(req,res) {
  res,send('Hello from C!');
}

app.get('/example',[cb0,cb1,cb2]);		//路由级挂载方式还支持:混合使用函数和函数数组处理路由

app.listen(3000, () => {
  console.log('running...');
});
  • 错误处理中间件(其实就是多了一个err参数):
app.use(function(err,req,res,next){
 console.error(err,stack);
 res.status(500).send('Something broke!');
});
  • 内置中间件(其实就是静态资源库):
app.use(express.static('public'));
  • 第三方中间件(举例:body-parser):

(1)参数传递——表单数据提交

//先利用命令(npm install body-parser)进行安装
let express = require('express');
const app = express();
const bodyParser = require('body-parser');

//挂载内置中间件
app.use(express.static('pubilc'));

//挂载post参数处理中间件(作用:处理application/x-www-form-urlencoded格式的数据,即表单发送post请求提交的数据)
app.use(bodyParser.urlencoded({ extended:false }));

//处理get提交的参数(不需要用到第三方中间件body-parser)
app.get('/login',(req,res) => {
  let data = req.query;		//返回的也是一个对象
  console.log(data);
  res.send('get data');
});

//处理post提交的参数(需要用到第三方中间件body-parser)
app.post('/login',(req,res) => {
  let data = req.body;
  console.log(data);		//返回的是一个对象
  if(data.username == 'admin' && data.password == '123'){
    res.send('success');
  }else{
    res.send('failure');
  }
  //res.send('OK');
});

app.listen(3000,() => {
  console.log('running...');
});
//新建一个public文件夹,让该文件夹成为静态资源库
//public/login.html
<body>
  <form action="http://localhost:3000/login" method="post">
    用户名:<input type="text" name="username" ><br>
    密  码:<input type="password" name="password"><br>
    <input type="submit" id="btn" value="提交">
  </form>
</body>
//public/login1.html
<body>
  <form action="http://localhost:3000/login" method="get">
    用户名:<input type="text" name="username" ><br>
    密  码:<input type="password" name="password"><br>
    <input type="submit" id="btn" value="提交">
  </form>
</body>

(2)参数传递——表单数据提交(通过Ajax提交)

//public/login-json.html
<head>
	<script type="text/javascript" src="jquery.js"></script>
  <script type="text/javascript">
  	$(function(){
      $('#btn').click(function(){
        $.ajax({
        type: 'get',		//post提交表单数据也可以
        url: 'http://localhost:3000/login',
        dataType: 'text',
        data: $('form:eq(0)').serialize(),		//serialize()方法通过序列化表单值,创建URL编码文本字符串。您可以选择一个或多个表单元素(比如:input及/或文本框),或者form元素本身(选择 <form> 标签本身进行序列化一般更容易些)。序列化的值可在生成 Ajax请求时用于URL查询字符串中。比如:下面的用户名和密码,它们都有自己的name属性,通过serialize()方法会得到一个字符串——username=andy&password=123。(注意:如果要表单元素的值包含到序列字符串中,元素必须使用name属性)
        success: function(data){
          console.log(data);
        }
      });
      });
    });
  </script>
</head>
<body>
  <form action="http://localhost:3000/login" method="get">
    用户名:<input type="text" name="username" ><br>
    密  码:<input type="password" name="password"><br>
    <input type="button" id="btn" value="提交">
  </form>
</body>




//先利用命令(npm install body-parser)进行安装
let express = require('express');
const app = express();
const bodyParser = require('body-parser');

//挂载内置中间件
app.use(express.static('pubilc'));

//挂载post参数处理中间件(作用:处理application/x-www-form-urlencoded格式的数据,即表单发送post请求提交的数据)
app.use(bodyParser.urlencoded({ extended:false }));

//处理get提交的参数(不需要用到第三方中间件body-parser)
app.get('/login',(req,res) => {
  let data = req.query;		//返回的也是一个对象
  console.log(data);
  res.send('get data');
});

//处理post提交的参数(需要用到第三方中间件body-parser)
app.post('/login',(req,res) => {
  let data = req.body;
  console.log(data);		//返回的是一个对象
  if(data.username == 'admin' && data.password == '123'){
    res.send('success');
  }else{
    res.send('failure');
  }
  //res.send('OK');
});

app.listen(3000,() => {
  console.log('running...');
});

(3)JSON数据提交(通过Ajax提交)

//public/login-json.html
<head>
	<script type="text/javascript" src="jquery.js"></script>
  <script type="text/javascript">
  	$(function(){
      $('#btn').click(function(){
        var obj = {
          username : $('#username').val(),
          password : $('#password').val()
        };
        $.ajax({
        type: 'post',		//这里的取值可以是get、post、put、delete
        url: 'http://localhost:3000/login',
        contentType: 'application/json',
        dataType: 'text',
        data: JSON.stringify(obj),
        success: function(data){
          console.log(data);
        }
      });
      });
    });
  </script>
</head>
<body>
  <form action="http://localhost:3000/login" method="get">
    用户名:<input type="text" name="username" id="username"><br>
    密  码:<input type="password" name="password" id="password"><br>
    <input type="button" id="btn" value="提交">
  </form>
</body>
//先利用命令(npm install body-parser)进行安装
let express = require('express');
const app = express();
const bodyParser = require('body-parser');

//挂载内置中间件
app.use(express.static('pubilc'));

//挂载post参数处理中间件(作用:处理application/x-www-form-urlencoded格式的数据,即表单发送post请求提交的数据)
app.use(bodyParser.urlencoded({ extended:false }));
//处理json格式的参数
app.use(bodyParser.json());

//处理get提交的参数(不需要用到第三方中间件body-parser)
app.get('/login',(req,res) => {
  let data = req.query;		//返回的也是一个对象
  console.log(data);
  res.send('get data');
});

//处理post提交的参数(需要用到第三方中间件body-parser)
app.post('/login',(req,res) => {
  let data = req.body;
  console.log(data);		//返回的是一个对象
  if(data.username == 'admin' && data.password == '123'){
    res.send('success');
  }else{
    res.send('failure');
  }
  //res.send('OK');
});

app.put('/login',(req,res) => {
  res.end('put data');
});

app.delete('/login',(req,res) => {
  res.end('delete data');
});

app.listen(3000,() => {
  console.log('running...');
});
  • 课外知识点:接口的测试可以通过postman完成,也可以通过Ajax完成,或者其他方式也可以。

本文标签: 进阶基础nodejs