Nodejs

目录

NodeJS基础与实战

Node是什么

Node是一个基于Chrome V8引擎的JavaScript代码运行环境

运行环境:

  • 浏览器(软件)能够运行JavaScript代码,浏览器就是JavaScript代码的运行环境
  • Node(软件)能够运行JavaScript代码,Node就是JavaScript代码的运行环境

Node环境搭建

Node.js运行环境安装

官网:https://nodejs.org/en/

image-20191224174204649

Node环境安装失败解决

1. 错误代号2502、2503

失败原因:系统帐户权限不足。

image-20191224174332202

解决办法:

1.以管理员身份运行powershell命令行工具

2.输入运行安装包命令 msiexec /package node安装包位置

2. 执行命令报错

image-20191224174435837

失败原因:Node安装目录写入环境变量失败

解决办法:将Node安装目录添加到环境变量中

PATH环境变量

存储系统中的目录,在命令行中执行命令的时候系统会自动去这些目录中查找命令的位置。

image-20191224174614646

比如:将迅雷的exe文件目录添加到系统变量path当中,在命令行中就可以用thunder来打开迅雷


Node快速入门

Node.js的组成

  • JavaScript 由三部分组成,ECMAScriptDOMBOM

  • Node.js是由ECMAScriptNode环境提供的一些附加API组成的,包括文件、网络、路径等等一些更加强大的 API。

image-20191224174911282

Node.js基础语法

  • 所有ECMAScript语法在Node环境中都可以使用

  • 在Node环境下执行代码,使用Node命令执行后缀为.js的文件即可

image-20191224174959568

命令行使用小技巧:

  • clear 清除命令行
  • node执行js的按tab自动补全
  • 在工作目录shift鼠标右键打开命令行免去切换到工作目录

Node.js全局对象global

浏览器中全局对象是window,在Node中全局对象是global

Node中全局对象下有以下方法,可以在任何地方使用,global可以省略。

  • console.log() 在控制台中输出
  • setTimeout() 设置超时定时器
  • clearTimeout() 清除超时时定时器
  • setInterval() 设置间歇定时器
  • clearInterval() 清除间歇定时器

Node的模块及第三方包

Node.js模块化开发

JavaScript开发弊端

JavaScript在使用时存在两大问题,文件依赖和命名冲突

image-20191224180630656

软件中的模块化开发

一个功能就是一个模块,多个模块可以组成完整应用,抽离一个模块不会影响其他功能的运行。

image-20191224180718868

Node.js中模块化开发规范

  • Node.js规定一个JavaScript文件就是一个模块,模块内部定义的变量和函数默认情况下在外部无法得到
  • 模块内部可以使用exports对象进行成员导出,使用require方法导入其他模块。

image-20191224180841949

模块成员导出

image-20191224180917581

模块成员的导入

image-20191224181015032

导入模块时.js后缀可以省略

模块成员导出的另一种方式

image-20191224181105214

exportsmodule.exports的别名(地址引用关系)导出对象最终以module.exports为准

模块导出两种方式的联系与区别

image-20191224181154443

导出多个成员时使用:

module.exports = { 
    name: 'zhangsan',
    ...
}

导出单个成员时使用:

exports.version = version;

系统模块

系统模块概述

Node运行环境提供的API. 因为这些API都是以模块化的方式进行开发的, 所以我们又称Node运行环境提供的API为系统模块

image-20191224181427276

系统模块fs文件操作

image-20191224181507048

举例:读取同目录下helloworld.js的内容

// 1.通过模块的名字fs对模块进行引用
const fs = require('fs');

// 2.通过模块内部的readFile读取文件内容
fs.readFile('./01.helloworld.js', 'utf8', (err, doc) => {
	console.log(err);
	console.log(doc);
});

注意:如果文件读取正确 err是 null

系统模块fs文件操作

image-20191224181835324

举例:向同级目录下demo.txt写内容,demo.js不存在则自动创建

const fs = require('fs');

fs.writeFile('./demo.txt', '即将要写入的内容', err => {
	if (err != null) {  //或 if(err)
		console.log(err);
		return;
	}

	console.log('文件内容写入成功');
})

系统模块path路径操作

为什么要进行路径拼接:

  • 不同操作系统的路径分隔符不统一
  • /public/uploads/avatar
  • Windows 上是 \ /
  • Linux 上是 /

路径拼接语法

image-20191224182308019

相对路径VS绝对路径

  • 大多数情况下使用绝对路径,因为相对路径有时候相对的是命令行工具的当前工作目录

  • 在读取文件或者设置文件路径时都会选择绝对路径

  • 使用__dirname获取当前文件所在的绝对路径

举例:读取绝对路径下的helloworld.js

const fs = require('fs');
const path = require('path');

console.log(__dirname);
console.log(path.join(__dirname, '01.helloworld.js'))

fs.readFile(path.join(__dirname, '01.helloworld.js'), 'utf8', (err, doc) => {
	console.log(err)
	console.log(doc)
});

第三方模块

别人写好的、具有特定功能的、我们能直接使用的模块即第三方模块,由于第三方模块通常都是由多个文件组成并且被放置在一个文件夹中,所以又名包。

第三方模块有两种存在形式:

  • 以js文件的形式存在,提供实现项目具体功能的API接口。

  • 以命令行工具形式存在,辅助项目开发

获取第三方模块:

  • 下载:npm install 模块名称

  • 卸载:npm unintall package 模块名称

全局安装与本地安装:

  • 命令行工具:全局安装

  • 库文件:本地安装

nodemon

nodemon是一个命令行工具,用以辅助项目开发。

在Node.js中,每次修改文件都要在命令行工具中重新执行该文件,非常繁琐。

使用步骤:

1.使用npm install nodemon –g 下载它

2.在命令行工具中用nodemon命令替代node命令执行文件

image-20191224193123952

nrm

nrm ( npm registry manager ):npm下载地址切换工具

npm默认的下载地址在国外,国内下载速度慢

使用步骤:

1.使用npm install nrm –g 下载它

2.查询可用下载地址列表 nrm ls

3.切换npm下载地址 nrm use 下载地址名称

Gulp

基于node平台开发的前端构建工具

将机械化操作编写成任务, 想要执行机械化操作时执行一个命令行命令任务就能自动执行了,用机器代替手工,提高开发效率。

Gulp作用
  • 项目上线,HTML、CSS、JS文件压缩合并

  • 语法转换(es6、less ...)

  • 公共文件抽离

  • 修改文件浏览器自动刷新

Gulp使用
  1. 使用npm install gulp下载gulp库文件

  2. 在项目根目录下建立gulpfile.js文件

  3. 重构项目的文件夹结构src目录放置源代码文件dist目录放置构建后文件

  4. 在gulpfile.js文件中编写任务

  5. 在命令行工具中执行gulp任务

注:要在命令行执行 npm install gulp-cli -g (全局安装gulp命令)

Gulp中提供的方法
  • gulp.src():获取任务要处理的文件
  • gulp.dest():输出文件
  • gulp.task():建立gulp任务
  • gulp.watch():监控文件的变化
Gulp插件
  • gulp-htmlmin :html文件压缩 (npm install gulp-htmlmin

  • gulp-csso :压缩css(npm install gulp-csso

  • gulp-babel :JavaScript语法转化(npm install gulp-babel @babel/core @babel/preset-env)

  • gulp-less: less语法转化(npm install gulp-less

  • gulp-uglify :压缩混淆JavaScript(npm install --save-dev gulp-uglify)

  • gulp-file-include 公共文件包含(npm install gulp-file-include

  • browsersync 浏览器实时同步

gulpfile.js代码具体实现:

// 引用gulp模块
const gulp = require('gulp');
const htmlmin = require('gulp-htmlmin');
const fileinclude = require('gulp-file-include');
const less = require('gulp-less');
const csso = require('gulp-csso');
const babel = require('gulp-babel');
const uglify = require('gulp-uglify');
// 使用gulp.task建立任务
// 1.任务的名称
// 2.任务的回调函数
gulp.task('first', () => {
	// 1.使用gulp.src获取要处理的文件
    //相当于复制操作
	gulp.src('./src/css/base.css')
		.pipe(gulp.dest('dist/css'));
});

// html任务
// 1.html文件中代码的压缩操作
// 2.抽取html文件中的公共代码
gulp.task('htmlmin', () => {
	gulp.src('./src/*.html')
		.pipe(fileinclude())
		// 压缩html文件中的代码
		.pipe(htmlmin({ collapseWhitespace: true }))
		.pipe(gulp.dest('dist'));
});

// css任务
// 1.less语法转换
// 2.css代码压缩
gulp.task('cssmin', () => {
	// 选择css目录下的所有less文件以及css文件
	gulp.src(['./src/css/*.less', './src/css/*.css'])
		// 将less语法转换为css语法
		.pipe(less())
		// 将css代码进行压缩
		.pipe(csso())
		// 将处理的结果进行输出
		.pipe(gulp.dest('dist/css'))
});

// js任务
// 1.es6代码转换
// 2.代码压缩
gulp.task('jsmin', () => {
	gulp.src('./src/js/*.js')
		.pipe(babel({
			// 它可以判断当前代码的运行环境 将代码转换为当前运行环境所支持的代码
            presets: ['@babel/env']
        }))
        .pipe(uglify())
        .pipe(gulp.dest('dist/js'))
});

// 复制文件夹
gulp.task('copy', () => {
	gulp.src('./src/images/*')
		.pipe(gulp.dest('dist/images'));

	gulp.src('./src/lib/*')
		.pipe(gulp.dest('dist/lib'))
});

// 构建任务
gulp.task('default', ['htmlmin', 'cssmin', 'jsmin', 'copy']);

在命令行输入:gulp default 就会依次执行命令

config

作用:允许开发人员将不同运行环境下的应用配置信息抽离到单独的文件中,模块内部自动判断当前应用的运行环境,并读取对应的配置信息,极大提供应用配置信息的维护成本,避免了当运行环境重复的多次切换时,手动到项目代码中修改配置信息:

  1. 使用npm install config命令下载模块

  2. 在项目的根目录下新建config文件夹

  3. 在config文件夹下面新建default.json、development.json、production.json文件

  4. 在项目中通过require方法,将模块进行导入

  5. 使用模块内部提供的get方法获取配置信息

  • development.json:
{
	"db": {
		"user": "itcast",
		"host": "localhost",
		"port": "27017",
		"name": "blog"
	}
}

将敏感配置信息存储在环境变量中:

  1. 在config文件夹中建立custom-environment-variables.json文件

  2. 配置项属性的值填写系统环境变量的名字

  3. 项目运行时config模块查找系统环境变量,并读取其值作为当前配置项属于的值

image-20200106100307884

// 引入mongoose第三方模块
const mongoose = require('mongoose');
// 导入config模块
const config = require('config');
console.log(config.get('db.host'))
// 连接数据库
mongoose.connect(`mongodb://${config.get('db.user')}:${config.get('db.pwd')}@${config.get('db.host')}:${config.get('db.port')}/${config.get('db.name')}`, {useNewUrlParser: true })
	.then(() => console.log('数据库连接成功'))
	.catch(() => console.log('数据库连接失败'))

区分开发环境与生产环境

电脑添加系统变量NODE_ENV,通过process.env获取来判断

if (process.env.NODE_ENV == 'development') {
     // 开发环境
 } else {
     // 生产环境
 }

bcrypt

bcrypt依赖的其他环境:

  1. python 2.x

  2. node-gyp

    • npm install -g node-gyp
  3. windows-build-tools

    • npm install --global --production windows-build-tools
// 导入bcrypt模块
const bcrypt = require('bcrypt');
// 生成随机字符串 gen => generate 生成 salt 盐
let salt = await bcrypt.genSalt(10);
// 使用随机字符串对密码进行加密
let pass = await bcrypt.hash('明文密码', salt);
// 密码比对
let isEqual = await bcrypt.compare('明文密码', '加密密码');


Joi

user.js:

// 验证用户信息
const validateUser = user => {
	// 定义对象的验证规则
	const schema = {
		username: Joi.string().min(2).max(12).required().error(new Error('用户名不符合验证规则')),
		email: Joi.string().email().required().error(new Error('邮箱格式不符合要求')),
		password: Joi.string().regex(/^[a-zA-Z0-9]{3,30}$/).required().error(new Error('密码格式不符合要求')),
		role: Joi.string().valid('normal', 'admin').required().error(new Error('角色值非法')),
		state: Joi.number().valid(0, 1).required().error(new Error('状态值非法'))
	};

	// 实施验证
	return Joi.validate(user, schema);
}

// 将用户集合做为模块成员进行导出
module.exports = {
	User,
	validateUser
}

// 引入用户集合的构造函数
const { User, validateUser } = require('../../model/user');
// 引入加密模块
const bcrypt = require('bcrypt');

module.exports = async (req, res, next) => {

	try {
		await validateUser(req.body)
	}catch (e) {
		// 验证没有通过
		// e.message
		// 重定向回用户添加页面
		// return res.redirect(`/admin/user-edit?message=${e.message}`);
		// JSON.stringify() 将对象数据类型转换为字符串数据类型
		return next(JSON.stringify({path: '/admin/user-edit', message: e.message}))
	}

	// 根据邮箱地址查询用户是否存在
	let user = await User.findOne({email: req.body.email});
	// 如果用户已经存在 邮箱地址已经被别人占用
	if (user) {
		// 重定向回用户添加页面
		// return res.redirect(`/admin/user-edit?message=邮箱地址已经被占用`);
		return next(JSON.stringify({path: '/admin/user-edit', message: '邮箱地址已经被占用'}))
	}
	// 对密码进行加密处理
	// 生成随机字符串
	const salt = await bcrypt.genSalt(10);
	// 加密
	const password = await bcrypt.hash(req.body.password, salt);
	// 替换密码
	req.body.password = password;
	// 将用户信息添加到数据库中
	await User.create(req.body);
	// 将页面重定向到用户列表页面
	res.redirect('/admin/user');
}

formidable

// 引入formidable模块
 const formidable = require('formidable');
 // 创建表单解析对象
 const form = new formidable.IncomingForm();
 // 设置文件上传路径
 form.uploadDir = "/my/dir";
 // 是否保留表单上传文件的扩展名
 form.keepExtensions = false;
 // 对表单进行解析
 form.parse(req, (err, fields, files) => {
     // fields 存储普通请求参数
         // files 存储上传的文件信息
 });

package.json

node_modules文件夹的问题:

  1. 文件夹以及文件过多过碎,当我们将项目整体拷贝给别人的时候,,传输速度会很慢很慢.

  2. 复杂的模块依赖关系需要被记录,确保模块的版本和当前保持一致,否则会导致当前项目运行报错

package.json文件的作用

项目描述文件,记录了当前项目信息,例如项目名称、版本、作者、github地址、当前项目依赖了哪些第三方模块等。

使用npm init -y命令生成。

项目依赖:

  • 在项目的开发阶段和线上运营阶段,都需要依赖的第三方包,称为项目依赖

  • 使用npm install 包名命令下载的文件会默认被添加到 package.json 文件的 dependencies 字段中

image-20191224230857856

开发依赖:

  • 在项目的开发阶段需要依赖,线上运营阶段不需要依赖的第三方包,称为开发依赖
  • 使用npm install 包名 --save-dev命令将包添加到package.json文件的devDependencies字段中

image-20191224230942559

package-lock.json文件的作用:

  • 锁定包的版本,确保再次下载时不会因为包版本不同而产生问题
  • 加快下载速度,因为该文件中已经记录了项目所依赖第三方包的树状结构和包的下载地址,重新安装时只需下载即可,不需要做额外的工作

总结:

  • 下载全部依赖:npm install

  • 下载项目依赖:npm install --production

Node.js中模块的加载机制

模块查找规则-当模块拥有路径但没有后缀时:

image-20191224231050726

  1. require方法根据模块路径查找模块,如果是完整路径,直接引入模块。

  2. 如果模块后缀省略,先找同名JS文件再找同名JS文件夹

  3. 如果找到了同名文件夹,找文件夹中的index.js

  4. 如果文件夹中没有index.js就会去当前文件夹中的package.json文件中查找main选项中的入口文件

  5. 如果找指定的入口文件不存在或者没有指定入口文件就会报错,模块没有被找到

模块查找规则-当模块没有路径且没有后缀时:

image-20191224231125020

  1. Node.js会假设它是系统模块

  2. Node.js会去node_modules文件夹中

  3. 首先看是否有该名字的JS文件

  4. 再看是否有该名字的文件夹

  5. 如果是文件夹看里面是否有index.js

  6. 如果没有就会去当前文件夹中的package.json文件中查找main选项中的入口文件

  7. 如果找指定的入口文件不存在或者没有指定入口文件就会报错,模块没有被找到


创建web服务器

image-20191229171401395

HTTP协议

报文

在HTTP请求和响应的过程中传递的数据块就叫报文,包括要传送的数据和一些附加信息,并且要遵守规定好的格式。

image-20191229173116011

请求报文

image-20191229173143346

响应报文

image-20191229173203616

还有方法设置响应头

response.setHeader('Content-Type', 'text/html');

当使用 response.setHeader()设置响应头时,它们将与传给 response.writeHead()的任何响应头合并,其中 response.writeHead()的响应头优先。

http请求与响应处理

Get请求参数处理

image-20191229173644773

代码:

// 用于创建网站服务器的模块
const http = require('http')
const url = require('url')
// app对象就是网站服务器对象
const app = http.createServer()
// 当客户端有请求来的时候
app.on('request', (req, res) => {
  // 获取请求报文信息
  // req.headers
  // console.log(req.headers['accept']);
  res.writeHead(200, {
    'content-type': 'text/html;charset=utf8'
  })
  console.log(req.url)
  // 1) 要解析的url地址
  // 2) 将查询参数解析成对象形式
  let { query, pathname } = url.parse(req.url, true)
  console.log(query.name)
  console.log(query.age)
  if (pathname == '/index' || pathname == '/') {
    res.end('<h2>欢迎来到首页</h2>')
  } else if (pathname == '/list') {
    res.end('welcome to listpage')
  } else {
    res.end('not found')
  }
  if (req.method == 'POST') {
    res.end('post')
  } else if (req.method == 'GET') {
    res.end('get')
  }
})
// 监听端口
app.listen(3000)
console.log('网站服务器启动成功')


POST请求参数

image-20191229184542589

// 用于创建网站服务器的模块
const http = require('http');
// app对象就是网站服务器对象
const app = http.createServer();
// 处理请求参数模块
const querystring = require('querystring');
// 当客户端有请求来的时候
app.on('request', (req, res) => {
	// post参数是通过事件的方式接受的
	// data 当请求参数传递的时候出发data事件
	// end 当参数传递完成的时候出发end事件
	let postParams = '';
	req.on('data', params => {
		postParams += params; //字符串键值对形式
	});
	req.on('end', () => {
		console.log(querystring.parse(postParams));
	});
	res.end('ok');

});
// 监听端口
app.listen(3000);
console.log('网站服务器启动成功');

路由

http://localhost:3000/index

http://localhost:3000/login

路由是指客户端请求地址与服务器端程序代码的对应关系。简单的说,就是请求什么响应什么。

image-20191229185704962

image-20191229185732800

静态资源与动态资源

客户端请求方式:

image-20191229192028544

静态访问:

const http = require('http')
const url = require('url')
const path = require('path')
const fs = require('fs')
const mime = require('mime') //需npm install mime
const app = http.createServer()

app.on('request', (req, res) => {
  // 获取用户的请求路径
  let { pathname } = url.parse(req.url)
  pathname = pathname == '/' ? '/default.html' : pathname
  // 将用户的请求路径转换为实际的服务器硬盘路径
  let realPath = path.join(__dirname, 'public' + pathname)
  let type = mime.getType(realPath)

  // 读取文件
  fs.readFile(realPath, (error, result) => {
    // 如果文件读取失败
    if (error) {
      res.writeHead(404, {
        'content-type': 'text/html;charset=utf8'
      })
      res.end('文件读取失败')
      return
    }
    res.writeHead(200, {
      'content-type': type
    })
    res.end(result)
  })
})

app.listen(3000)
console.log('服务器启动成功')



Node.js异步编程

使用回调函数获取异步API执行结果

image-20191229192259176

Node.js中的异步API

image-20191229192403807

回调地狱

var fs = require('fs')

fs.readFile('./data/a.txt', 'utf8', function(err, data) {
  if (err) {
    // return console.log('读取失败')
    // 抛出异常
    //    1. 阻止程序的执行
    //    2. 把错误消息打印到控制台
    throw err
  }
  console.log(data)
  fs.readFile('./data/b.txt', 'utf8', function(err, data) {
    if (err) {
      throw err
    }
    console.log(data)
    fs.readFile('./data/c.txt', 'utf8', function(err, data) {
      if (err) {
        throw err
      }
      console.log(data)
    })
  })
})


Promise

Promise-api简单使用

var fs = require('fs')

var p1 = new Promise(function (resolve, reject) {
  fs.readFile('./data/a.txt', 'utf8', function (err, data) {
    if (err) {
      reject(err)
    } else {
      resolve(data)
    }
  })
})

var p2 = new Promise(function (resolve, reject) {
  fs.readFile('./data/b.txt', 'utf8', function (err, data) {
    if (err) {
      reject(err)
    } else {
      resolve(data)
    }
  })
})

var p3 = new Promise(function (resolve, reject) {
  fs.readFile('./data/c.txt', 'utf8', function (err, data) {
    if (err) {
      reject(err)
    } else {
      resolve(data)
    }
  })
})

p1
  .then(function (data) {
    console.log(data)
    // 当 p1 读取成功的时候
    // 当前函数中 return 的结果就可以在后面的 then 中 function 接收到
    // 当你 return 123 后面就接收到 123
    //      return 'hello' 后面就接收到 'hello'
    //      没有 return 后面收到的就是 undefined
    // 上面那些 return 的数据没什么卵用
    // 真正有用的是:我们可以 return 一个 Promise 对象
    // 当 return 一个 Promise 对象的时候,后续的 then 中的 方法的第一个参数会作为 p2 的 resolve
    // 
    return p2
  }, function (err) {
    console.log('读取文件失败了', err)
  })
  .then(function (data) {
    console.log(data)
    return p3
  })
  .then(function (data) {
    console.log(data)
    console.log('end')
  })


promise封装

var fs = require('fs')

function pReadFile(filePath) {
  return new Promise(function (resolve, reject) {
    fs.readFile(filePath, 'utf8', function (err, data) {
      if (err) {
        reject(err)
      } else {
        resolve(data)
      }
    })
  })
}

pReadFile('./data/a.txt')
  .then(function (data) {
    console.log(data)
    return pReadFile('./data/b.txt')
  })
  .then(function (data) {
    console.log(data)
    return pReadFile('./data/c.txt')
  })
  .then(function (data) {
    console.log(data)
  })


注意:捕获错误

promise.then((result) => {
	 console.log(result);
})
.catch((err)=> {
	console.log(err);
})

//或
promise.then((result) => {
	 console.log(result);
},(err)=>{
    console.log(err);
})


promise应用场景

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>

<body>
  <form action="00-js中的一等公民函数.js" id="user_form">
  </form>
  <script type="text/html" id="tpl">
    <div>
      <label for="">用户名</label>
      <input type="text" value="{{ user.username }}">
    </div>
    <div>
      <label for="">年龄</label>
      <input type="text" value="{{ user.age }}">
    </div>
    <div>
      <label for="">职业</label>
      <select name="" id="">
        {{ each jobs }} {{ if user.job === $value.id }}
        <option value="{{ $value.id }}" selected>{{ $value.name }}</option>
        {{ else }}
        <option value="{{ $value.id }}">{{ $value.name }}</option>
        {{ /if }} {{ /each }}
      </select>
    </div>
  </script>
  <script src="node_modules/art-template/lib/template-web.js"></script>
  <script src="node_modules/jquery/dist/jquery.js"></script>
  <script>
    // 用户表
    //  其中一个接口获取用户数据
    //  职业:2
    // 职业信息表
    //  其中一个接口获取所有的职业信息
    
    //封装的ajax方法回调方式
     get('http://127.0.0.1:3000/users/4', function (userData) {
      get('http://127.0.0.1:3000/jobs', function (jobsData) {
        var htmlStr = template('tpl', {
          user: JSON.parse(userData),
          jobs: JSON.parse(jobsData)
        })
        console.log(htmlStr)
        document.querySelector('#user_form').innerHTML = htmlStr
      })
    })

    //jquery中的ajax方法支持promise写法
    var data = {}
    $.get('http://127.0.0.1:3000/users/4')
      .then(function (user) {
        data.user = user
        return $.get('http://127.0.0.1:3000/jobs')
      })
      .then(function (jobs) {
        data.jobs = jobs
        var htmlStr = template('tpl', data)
        document.querySelector('#user_form').innerHTML = htmlStr
      })

    //封装的ajax支持promise
    var data = {}
    pGet('http://127.0.0.1:3000/users/4')
      .then(function (user) {
        data.user = user
        return pGet('http://127.0.0.1:3000/jobs')
      })
      .then(function (jobs) {
        data.jobs = jobs
        var htmlStr = template('tpl', data)
        document.querySelector('#user_form').innerHTML = htmlStr
      })

    //封装的ajax支持promise
    function pGet(url, callback) {
      return new Promise(function (resolve, reject) {
        var oReq = new XMLHttpRequest()
        // 当请求加载成功之后要调用指定的函数
        oReq.onload = function () {
          // 我现在需要得到这里的 oReq.responseText
          callback && callback(JSON.parse(oReq.responseText))
          resolve(JSON.parse(oReq.responseText))
        }
        oReq.onerror = function (err) {
          reject(err)
        }
        oReq.open("get", url, true)
        oReq.send()
      })
    }
    
    // 这个 get 是 callback 方式的 API
    // 可以使用 Promise 来解决这个问题
    function get(url, callback) {
      var oReq = new XMLHttpRequest()
      oReq.onload = function () {
        callback(oReq.responseText)
      }
      oReq.open("get", url, true)
      oReq.send()
    }
  </script>
</body>

</html>


异步函数async

异步函数是异步编程语法的终极解决方案,它可以让我们将异步代码写成同步的形式,让代码不再有回调函数嵌套,使代码变得清晰明了。

async关键字

image-20191229211557311

await关键字

  1. await关键字只能出现在异步函数中

  2. await promise await后面只能写promise对象 写其他类型的API是不不可以的

  3. await关键字可是暂停异步函数向下执行 直到promise返回结果

const fs = require('fs');
// 改造现有异步函数api 让其返回promise对象 从而支持异步函数语法
const { promisify } = require('util')
// 调用promisify方法改造现有异步API 让其返回promise对象
const readFile = promisify(fs.readFile);

async function run () {
	let r1 = await readFile('./1.txt', 'utf8')
	let r2 = await readFile('./2.txt', 'utf8')
	let r3 = await readFile('./3.txt', 'utf8')
	console.log(r1)
	console.log(r2)
	console.log(r3)
}
run();


mongoDB

环境搭建

  • 使用Node.js操作MongoDB数据库需要依赖Node.js第三方包mongoose

  • 项目目录下使用npm install mongoose命令下载

但首先我们得启动数据库服务,否则MongoDB将无法连接。默认是开启的。使用命令行工具:net start mongoDB

数据库连接与创建

简单使用:

// 引入mongoose第三方模块 用来操作数据库
const mongoose = require('mongoose')
// 数据库连接
mongoose
  .connect('mongodb://localhost/playground', {
    useUnifiedTopology: true,
    useNewUrlParser: true
  })
  // 连接成功
  .then(() => console.log('数据库连接成功'))
  // 连接失败
  .catch(err => console.log(err, '数据库连接失败'))

// 创建集合规则
const courseSchema = new mongoose.Schema({
  name: String,
  author: String,
  isPublished: Boolean
})

const Course = mongoose.model('Course', courseSchema) // 创建构造函数
Course.create({
  name: 'Javascript123',
  author: '黑马讲师',
  isPublished: false
}).then(result => {
  console.log(result)//返回插入文档的数据 对象格式
})

//或使用:
const course = new Course({
  name: 'node.js基础',
  author: '黑马讲师',
  isPublished: true
})
// 将文档插入到数据库中
course.save()


mongoose验证

创建集合规则时,可以设置当前字段的验证规则,验证失败就则输入插入失败。

  • required: true 必传字段

  • minlength:3 字符串最小长度

  • maxlength: 20 字符串最大长度

  • min: 2 数值最小为2

  • max: 100 数值最大为100

  • enum: ['html', 'css', 'javascript', 'node.js']

  • trim: true 去除字符串两边的空格

  • validate: 自定义验证器

  • default: 默认值

  • message 自定义错误信息

获取错误信息:error.errors['字段名称'].message

使用:

const postSchema = new mongoose.Schema({
  title: {
    type: String,
    // 必选字段
    required: [true, '请传入文章标题'],
    // 字符串的最小长度
    minlength: [2, '文章长度不能小于2'],
    // 字符串的最大长度
    maxlength: [5, '文章长度最大不能超过5'],
    // 去除字符串两边的空格
    trim: true
  },
  age: {
    type: Number,
    // 数字的最小范围
    min: 18,
    // 数字的最大范围
    max: 100
  },
  publishDate: {
    type: Date,
    // 默认值
    default: Date.now
  },
  category: {
    type: String,
    // 枚举 列举出当前字段可以拥有的值
    enum: {
      values: ['html', 'css', 'javascript', 'node.js'],
      message: '分类名称要在一定的范围内才可以'
    }
  },
  author: {
    type: String,
    validate: {
      validator: v => {
        // 返回布尔值
        // true 验证成功
        // false 验证失败
        // v 要验证的值
        return v && v.length > 4
      },
      // 自定义错误信息
      message: '传入的值不符合验证规则'
    }
  }
})

const Post = mongoose.model('Post', postSchema)

Post.create({ title: 'aa', age: 60, category: 'java', author: 'bd' })
  .then(result => console.log(result))
  .catch(error => {
    // 获取错误信息对象
    const err = error.errors
    // 循环错误信息对象
    for (var attr in err) {
      // 将每个字段的错误信息并打印到控制台中
      console.log(err[attr]['message'])
    }
  })

增删改查

方式一:

  • 创建schema(表的规则)使用new mongoose.Schema
  • 创建集合(构造函数) mongoose.model('表名',schema)
  • 使用Course.create({...})或用new Couese({...}) 最后调用save()方法
const courseSchema = new mongoose.Schema({...})
const Course = mongoose.model('Course', courseSchema) // courses

Course.create({
  name: 'Javascript123',
  author: '黑马讲师',
  isPublished: false
}).then(result => {
  	console.log(result)//返回插入文档的数据 对象格式
}).catch(err=>{
   console.log(err)
})

//或使用
const course = new Course({
  name: 'node.js基础',
  author: '黑马讲师',
  isPublished: true
})
// 将文档插入到数据库中
course.save()


方式二:导入现有数据表

mongoimport –d 数据库名称 –c 集合名称 –file 要导入的数据文件(相对于命令行工具路径)

注:先找到mongodb数据库的安装目录,将安装目录下的bin目录放置在环境变量中才能使用mongoimport 命令

//查询用户集合中的所有文档
User.find().then(result => console.log(result));//数组

//通过_id字段查找文档(条件查询)
User.find({_id: '5c09f267aeb04b22f8460968'}).then(result => console.log(result)) //数组

//findOne方法返回一条文档 默认返回当前集合中的第一条文档
User.findOne({name: '李四'}).then(result => console.log(result)) //对象

//查询用户集合中年龄字段大于20并且小于40的文档
User.find({age: {$gt: 20, $lt: 40}}).then(result => console.log(result))

//查询用户集合中hobbies字段值包含足球的文档
User.find({hobbies: {$in: ['足球']}}).then(result => console.log(result))

//选择要查询的字段
User.find().select('name email -_id').then(result => console.log(result))

//根据年龄字段进行升序排列
User.find().sort('age').then(result => console.log(result))

//根据年龄字段进行降序排列
User.find().sort('-age').then(result => console.log(result))

// 查询文档跳过前两条结果 限制显示3条结果
User.find()
  .skip(2)
  .limit(3)
  .then(result => console.log(result))

更新

image-20191231101339454

image-20191231101621756

updateMany第一个传空对象表示全部数据

result返回:

image-20191231101513052

// 查找到一条文档并且删除
// 返回删除的文档
// 如何查询条件匹配了多个文档 那么将会删除第一个匹配的文档
 User.findOneAndDelete({_id: '5c09f267aeb04b22f8460968'}).then(result => console.log(result))

// 删除多条文档
User.deleteMany({}).then(result => console.log(result))

集合关联

通常不同集合的数据之间是有关系的,例如文章信息和用户信息存储在不同集合中,但文章是某个用户发表的,要查询文章的所有信息包括发表用户,就需要用到集合关联。

  • 使用id对集合进行关联

  • 使用populate方法进行关联集合查询

image-20200101101459347

代码示例:

// 用户集合规则
const userSchema = new mongoose.Schema({
  name: {
    type: String,
    required: true
  }
})
// 文章集合规则
const postSchema = new mongoose.Schema({
  title: {
    type: String
  },
  author: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'User'
  }
})
// 用户集合
const User = mongoose.model('User', userSchema)
// 文章集合
const Post = mongoose.model('Post', postSchema)

// 创建用户
User.create({name: 'itheima'}).then(result => console.log(result));
// 创建文章
Post.create({titile: '123', author: '5c0caae2c4e4081c28439791'}).then(result => console.log(result));

Post.find()
  .populate('author')
  .then(result => console.log(result))

mongoDB数据库添加账号

  1. 以系统管理员的方式运行powershell

  2. 连接数据库 mongo

  3. 查看数据库 show dbs

  4. 切换到admin数据库 use admin

  5. 创建超级管理员账户 db.createUser()

  6. 切换到blog数据 use blog

  7. 创建普通账号 db.createUser()

  8. 卸载mongodb服务

    • 停止服务 net stop mongodb
    • mongod --remove
  9. 创建mongodb服务

  • mongod --logpath="C:\Program Files\MongoDB\Server\4.1\log\mongod.log" (mongodb安装目录)--dbpath="C:\ProgramFiles\MongoDB\Server\4.1\data" --install –-auth
  1. 启动mongodb服务 net start mongodb

  2. 在项目中使用账号连接数据库

  • mongoose.connect('mongodb://user:pass@localhost:port/database')

Node中使用模板引擎

首先:在命令行工具中使用 npm install art-template 命令进行下载

image-20200101101856076

条件判断:

image-20200101101952186

子模版

使用子模板可以将网站公共区块(头部、底部)抽离到单独的文件中。

image-20200101102043230

image-20200101103256849

代码示例:

04.js

const template = require('art-template');
const path = require('path');

const views = path.join(__dirname, 'views', '04.art');

const html = template(views, {
	msg: '我是首页'
});

console.log(html);


view文件夹下的04.art

{{ include './common/header.art' }}
//<% include('./common/header.art') %>

<div> {{ msg }} </div>

{{ include './common/footer.art' }}
//<% include('./common/footer.art') %>

view文件夹下common文件夹写的header,footer

我是头部
我是底部

模板继承

使用模板继承可以将网站HTML骨架抽离到单独的文件中,其他页面模板可以继承骨架文件。

模板继承示例

image-20200101102138578

模板继承示例

image-20200101102200743

代码示例:

image-20200101103256849

05.js

const template = require('art-template');
const path = require('path');

const views = path.join(__dirname, 'views', '05.art');

const html = template(views, {
	msg: '首页模板'
});

console.log(html);

views文件夹下05.art

{{extend './common/layout.art'}}

{{block 'content'}}

<p>{{ msg }}</p>

{{/block}}

{{block 'link'}}
<link rel="stylesheet" type="text/css" href="./main.css">
{{/block}}

common文件夹下的layout.art

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Document</title>
	{{block 'link'}} {{/block}}
</head>
<body>
	{{block 'content'}} {{/block}}
</body>
</html>

模板配置

1.向模板中导入变量 template.defaults.imports.变量名 = 变量值;

2.设置模板根目录 template.defaults.root = 模板目录

3.设置模板默认后缀 template.defaults.extname = '.art'

导入变量dateformat格式化时间

首先:项目文件下 npm install dateformat

代码示例:

06.js

const template = require('art-template');
const path = require('path');
const dateFormat = require('dateformat');

// 设置模板的根目录
template.defaults.root = path.join(__dirname, 'views');

// 导入模板变量
template.defaults.imports.dateFormat = dateFormat;

// 配置模板的默认后缀
template.defaults.extname = '.html'; //设置了的话 模板引擎渲染的时候可省略文件后缀

const html = template('06.art', {
	time: new Date()
});

console.log(template('07', {}));
console.log(html);

views文件夹下的06.art

{{ dateFormat(time, 'yyyy-mm-dd')}}

案列

案例目录:

image-20200101105804075

第三方模块router:

  1. 获取路由对象

  2. 调用路由对象提供的方法创建路由

  3. 启用路由,使路由生效

image-20200101110001250

第三方模块serve-static:

  1. 引入serve-static模块获取创建静态资源服务功能的方法

  2. 调用方法创建静态资源服务并指定静态资源服务目录

  3. 启用静态资源服务功能

image-20200101110106961

入口函数app.js

// 引入http模块
const http = require('http');
// 引入模板引擎
const template = require('art-template');
// 引入path模块
const path = require('path');
// 引入静态资源访问模块
const serveStatic = require('serve-static');
// 引入处理日期的第三方模块
const dateformat = require('dateformat');

const router = require('./route/index');
// 实现静态资源访问服务
const serve = serveStatic(path.join(__dirname, 'public'))

// 配置模板的根目录
template.defaults.root = path.join(__dirname, 'views');
// 处理日期格式的方法
template.defaults.imports.dateformat = dateformat;

// 数据库连接
require('./model/connect');

// 创建网站服务器
const app = http.createServer();
// 当客户端访问服务器端的时候
app.on('request', (req, res) => {
	// 启用路由功能
	router(req, res, () => {})
	// 启用静态资源访问服务功能
	serve(req, res, () => {})
});
// 端口监听
app.listen(80);
console.log('服务器启动成功');

数据库连接: model文件夹下connect.js

const mongoose = require('mongoose');
// 连接数据库
mongoose.connect('mongodb://localhost/playground', { useNewUrlParser: true })
	.then(() => console.log('数据库连接成功'))
	.catch(() => console.log('数据库连接失败'))

数据库创建表: model文件夹下user.js

const mongoose = require('mongoose');
// 创建学生集合规则
const studentsSchema = new mongoose.Schema({
	name: {
		type: String,
		required: true,
		minlength: 2,
		maxlength: 10
	},
	age: {
		type: Number,
		min: 10,
		max: 25
	},
	sex: {
		type: String
	},
	email: String,
	hobbies: [ String ],
	collage: String,
	enterDate: {
		type: Date,
		default: Date.now
	}
});
// 创建学生信息集合
const Student = mongoose.model('Student', studentsSchema);

// 将学生信息集合进行导出
module.exports = Student;

路径处理:route文件夹下index.js

// 引入router模块
const getRouter = require('router');
// 获取路由对象
const router = getRouter();
// 学生信息集合
const Student = require('../model/user');
// 引入模板引擎
const template = require('art-template');
// 引入querystring模块
const querystring = require('querystring');

// 呈递学生档案信息页面
router.get('/add', (req, res) => {
	let html = template('index.art', {});
	res.end(html);
})

// 呈递学生档案信息列表页面
router.get('/list', async (req, res) =>{
	// 查询学生信息
	let students = await Student.find();
	console.log(students);
	let html = template('list.art', {
		students: students
	})
	res.end(html)
})
// 实现学生信息添加功能路由
router.post('/add', (req, res) => {
	// 接收post请求参数
	let formData = '';
	req.on('data', param => {
		formData += param;
	});
	req.on('end', async () => {
		await Student.create(querystring.parse(formData))
		res.writeHead(301, {
			Location: '/list'
		});
		res.end()
	})
});

module.exports = router;

views文件夹下index.art

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
	<title>学生档案</title>
	<link rel="stylesheet" href="./css/main.css">
</head>
<body>
	<form action="/add" method="post">
		<fieldset>
			<legend>学生档案</legend>
			<label>
				姓名: <input class="normal" type="text" autofocus placeholder="请输入姓名" name="name">
			</label>
			<label>
				年龄: <input class="normal"  type="text" placeholder="请输入年龄" name="age">
			</label>
			<label>
				性别: 
				<input type="radio" value="0" name="sex"> 男
				<input type="radio" value="1" name="sex"> 女
			</label>
			<label>
				邮箱地址: <input class="normal" type="text" placeholder="请输入邮箱地址" name="email">
			</label>
			<label>
				爱好: 
				<input type="checkbox" value="敲代码" name="hobbies"> 敲代码
				<input type="checkbox" value="打篮球" name="hobbies"> 打篮球
				<input type="checkbox" value="睡觉" name="hobbies"> 睡觉
			</label>
			<label>
				所属学院: 
				<select class="normal" name="collage">
					<option value="前端与移动开发">前端与移动开发</option>
					<option value="PHP">PHP</option>
					<option value="JAVA">JAVA</option>
					<option value="Android">Android</option>
					<option value="IOS">IOS</option>
					<option value="UI设计">UI设计</option>
					<option value="C++">C++</option>
				</select>
			</label>
			<label>
				入学日期: <input type="date" class="normal" name="enterDate">
			</label>
			<label class="btn">
				<input type="submit" value="提交" class="normal">
			</label>
		</fieldset>
	</form>
</body>
</html>

views文件夹下list.art

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>学员信息</title>
	<link rel="stylesheet" href="./css/list.css">
</head>
<body>
	<table>
		<caption>学员信息</caption>
		<tr>
			<th>姓名</th>
			<th>年龄</th>
			<th>性别</th>
			<th>邮箱地址</th>
			<th>爱好</th>
			<th>所属学院</th>
			<th>入学时间</th>
		</tr>
		{{each students}}
			<tr>
				<th>{{$value.name}}</th>
				<th>{{$value.age}}</th>
				<th>{{$value.sex == '0' ? '男' : '女'}}</th>
				<th>{{$value.email}}</th>
				<th>
					{{each $value.hobbies}}
						<span>{{$value}}</span>
					{{/each}}
				</th>
				<th>{{$value.collage}}</th>
				<th>{{dateformat($value.enterDate, 'yyyy-mm-dd')}}</th>
			</tr>
		{{/each}}
		
	</table>
</body>
</html>


Express框架

概述

Express是一个基于Node平台的web应用开发框架,它提供了一系列的强大特性,帮助你创建各种Web应用。我们可以使用 npm install express 命令进行下载。

特性:

  • 提供了方便简洁的路由定义方式

  • 对获取HTTP请求参数进行了简化处理

  • 对模板引擎支持程度高,方便渲染动态HTML页面

  • 提供了中间件机制有效控制HTTP请求

  • 拥有大量第三方中间件对功能进行扩展

路由处理

原生node与express路由处理对比:

image-20200101190613752

请求参数处理

原生node与express请求参数处理对比:

image-20200101190825170

初体验

// 引入express框架
const express = require('express');
// 创建网站服务器
const app = express();
// send()
// 1. send方法内部会检测响应内容的类型
// 2. send方法会自动设置http状态码
// 3. send方法会帮我们自动设置响应的内容类型及编码
app.get('/' , (req, res) => {
	res.send('Hello. Express');
})
//可直接发送对象
app.get('/list', (req, res) => {
	res.send({name: '张三', age: 20})
})

app.listen(3000);
console.log('网站服务器启动成功');

中间件

中间件就是一堆方法,可以接收客户端发来的请求、可以对请求做出响应,也可以将请求继续交给下一个中间件继续处理。

image-20200101191207455

概述

中间件主要由两部分构成,中间件方法以及请求处理函数

中间件方法由Express提供,负责拦截请求,请求处理函数由开发人员提供,负责处理请求。

image-20200101191251719

可以针对同一个请求设置多个中间件,对同一个请求进行多次处理。默认情况下,请求从上到下依次匹配中间件,一旦匹配成功,终止匹配。可以调用next方法将请求的控制权交给下一个中间件,直到遇到结束请求的中间件。

image-20200101191434015

app.use

image-20200101191852873

应用

  1. 路由保护,客户端在访问需要登录的页面时,可以先使用中间件判断用户登录状态,用户如果未登录,则拦截请求,直接响应,禁止用户进入需要登录的页面。

代码示例:

app.js中:
// 导入express-session模块
const session = require('express-session');
// 拦截请求 判断用户登录状态
app.use('/admin', require('./middleware/loginGuard'));


loginGuard.js中:
const guard = (req, res, next) => {
	// 判断用户访问的是否是登录页面
	// 判断用户的登录状态
	// 如果用户是登录的 将请求放行
	// 如果用户不是登录的 将请求重定向到登录页面
	if (req.url != '/login' && !req.session.username) {
		res.redirect('/admin/login');
	} else {
		// 如果用户是登录状态 并且是一个普通用户
		if (req.session.role == 'normal') {
			// 让它跳转到博客首页 阻止程序向下执行
			return res.redirect('/home/')
		}
		// 用户是登录状态 将请求放行
		next();
	}
}

module.exports = guard;

  1. 网站维护公告,在所有路由的最上面定义接收所有请求的中间件,直接为客户端做出响应,网站正在维护中。
  2. 自定义404页面

错误处理中间件

image-20200101192418812

捕获错误

image-20200101193519140

处理请求

模块化路由

作用:模块化路由,方便处理二级路由

image-20200101193225091

GET参数的获取

Express框架中使用req.query即可获取GET参数,框架内部会将GET参数转换为对象并返回。

image-20200101193354950

POST参数的获取

Express中接收post请求参数需要借助第三方包 body-parser。

// 引入express框架
const express = require('express');
const bodyParser = require('body-parser');
// 创建网站服务器
const app = express();
// 拦截所有请求
// extended: false 方法内部使用querystring模块处理请求参数的格式
// extended: true 方法内部使用第三方模块qs处理请求参数的格式
app.use(bodyParser.urlencoded({extended: false}))

app.post('/add', (req, res) => {
	// 接收post请求参数
	res.send(req.body)
})

// 端口监听
app.listen(3000);

app.use方法

app.use(fn ({a: 2}))

function fn (obj) {
	return function (req, res, next) {
		if (obj.a == 1) {
			console.log(req.url)
		}else {
			console.log(req.method)
		}
		next()
	}
}

app.get('/', (req, res) => {
	// 接收post请求参数
	res.send('ok')
})

Express路由参数

image-20200101193951497

静态资源的处理

通过Express内置的express.static可以方便地托管静态文件,例如img、CSS、JavaScript 文件等。

const express = require('express');
const path = require('path');
const app = express();

// 实现静态资源访问功能
app.use(express.static(path.join(__dirname, 'public')))

// 端口监听
app.listen(3000);

express-art-template

  • 为了使art-template模板引擎能够更好的和Express框架配合,模板引擎官方在原art-template模板引擎的基础上封装了express-art-template。

  • 使用npm install art-template express-art-template命令进行安装。

app.locals 对象

const express = require('express');
const path = require('path');
const app = express();
// 模板配置
app.engine('art', require('express-art-template'))
app.set('views', path.join(__dirname, 'views'))
app.set('view engine', 'art');

app.locals.users = [{
	name: 'zhangsan',
	age: 20
},{
	name: '李四',
	age: 30
}]

app.get('/index', (req, res) => {
	res.render('index', {
		msg: '首页'
	})
});

app.get('/list', (req, res) => {
	res.render('list', {
		msg: '列表页'
	});
})
// 端口监听
app.listen(3000);

index.art就可以拿到公共数据users

image-20200101213725176

express-session

const session = require('express-session');
app.use(session({ secret: 'secret key' }));

用户登陆成功后将信息存储再req.session中实现登录拦截

image-20200106101948572

express-formidable

const express = require('express');
const formidableMiddleware = require('express-formidable');
app.use(formidableMiddleware({
	// 文件上传目录
	uploadDir: path.join(__dirname, 'public', 'uploads'),
	// 最大上传文件为2M
	maxFileSize: 2 * 1024 * 1024,
	// 保留文件扩展名
	keepExtensions: true
}));

posted @ 2019-12-24 19:50  garxirapper  阅读(644)  评论(0编辑  收藏  举报