💥此MD/PDF/HTML文档由 @竹梦者也编辑制作,不可贩卖❌。仅供学习交流使用,下载后切勿随意传播。转载麻烦加上出处~
📌欢迎在留言区提供修改建议~
💌本文内容和代码来自于
tianyu
老师的Node教程
✅2021.7.12 @竹梦者也(https://www.zjgsuzjx.top)
✅2021.7.18 更新v1.1,完善了模块化内容
一、Node.js一、基本使用1. 概念2. 特点3. 与浏览器端区别4. 安装5. 环境配置6. 用Node运行JS二、模块化1. 概念2. CommonJS# 浏览器端3. ES6模块化# 暴露# 引入# 进阶使用# Babel4. AMD模块化5. CMD模块化三、包和包管理器1. package包# 包结构# 包描述文件# 创建一个包2. NPM# 作用# 常用指令3. cnpm# 概念# 安装4. Yarn# 概念# 特点# 安装# 修改Yarn的全局安装和缓存位置# 常用指令四、Buffer缓冲器1. 概念2. 使用# 第一种# 第二种# 第三种3. 数据存入实例五、fs文件系统1. 概念2. 使用# 简单文件的写入# 流式文件写入# 简单文件读取# 流式文件读取二、数据库一、基本概念1. 含义2. 用途3. 分类# 关系型数据库(RDBS)# 非关系型数据库(NoSQL)二、MongoDB1. 简介2. 安装3. 安装图形化工具4. Navicat使用教程5. MongoDB的使用# 简介# 基本命令# 操作命令6. 数据库的CRUD7. Mongoose# 简介# 优势# 使用# CRUD8. mongoose的模块化三、Express一、原生Node服务器1. 服务器搭建2. 服务器响应二、Express使用1. 概念2. 下载3. 搭建服务器4. Route路由# 概念# 组成部分# 实例# Request对象# Response对象5. 中间件# 简介# 功能# 分类# 应用层中间件# 第三方中间件# 内置中间件# 注册登录页面案例# Router路由器三、EJS模板1. 概念2. 安装与使用四、cookie1. 概念2. 分类3. 工作原理4. 应用5. 简单使用五、session1. 概念2. 特点3. 工作流程4. 数据加密# md5# sha1
Node.js 是一个基于 Chrome V8 引擎的 JavaScript
运行环境。
使得让JS代码在本地也能运行,从而能够操作本地的文件。原理是将JS代码转译为C/C++代码,从而能够实现socket(网络编程)、http、文件系统、etc(编译环境)、事件循环模型、异步操作。
优点:
1) 异步非阻塞的I/O(I/O线程池)
2) 特别适用于I/O密集型应用(频繁I/O操作)
3) 事件循环机制
4) 单线程(成也单线程,败也单线程)
5) 跨平台
缺点:
1) 回调函数嵌套太多、太深(俗称回调地狱)
2) 单线程,处理不好CPU 密集型任务(Java解决高并发是提升配置,Node解决高并发是用回调函数;CPU每个请求太慢时,会造成Node排队时间过长)
CPU密集型与I/O密集型的区别:
CPU密集型:需要过多的判断,事情不明确。
I/O密集型:不需要过多的判断,事情明确。
在浏览器端:js由BOM、DOM、ES规范组成。
在Node端:js只由ES(所有规范)和global(Node内置属性,相当于window)组成。
左边是正式服,右边是测试服。
安装完成之后,打开命令行窗口,输入node -v
查看当前node版本。
https://www.cnblogs.com/zhouyu2017/p/6485265.html
环境配置的目的是为了在cmd中能够直接使用指令。如node、yarn等等。
方法1:
在cmd里,cd到JS文件所在文件夹。再输入node xxx.js
。
方法2:
打开我的电脑到指定文件夹,在路径中直接输入cmd
并回车,后面跟上面同理。
方法3:
在webstorm里配置好node环境,直接右键运行js文件。
File ---> Settings ---> Languages & Frameworks ---> Node.js and NPM
方法4:
按住shift + 右键,点击在此处打开命令行。再输入node xxx.js
。
模块化指的就是将一个大的功能拆分为一个一个小的模块,通过不同的模块的组合来实现一个大功能。
Node中使用的是
CommonJS
规范来实现的模块化,前端使用的模块化规范是AMD
和CMD
。
CommonJS 是一套模块化规范,它包含:模块、二进制、Buffer、字符集编码、I/O流、进程环境、文件系统、套接字、单元测试、Web服务器网关接口、包管理等。
补充:CommonJS是唯一一个可以在双端(浏览器、node)运行的模块化规范。
补充2:服务器端模块的加载时同步的。
补充3:暴露的本质是
module.exports
所指向的那个对象。
在CommonJS模块规范中,module.exports
和exports.xxx
不能混用。如果混用则以module.exports
为主。
模块引用
通过require()函数来引入外部的模块。
1var a = require("../a");
2var math = require("math");
它需要一个模块的标识作为参数,返回一个对象表示引入的模块
补充:在引入的同时可以解构赋值。
注意:引入第三方模块时,直接写包名;引入自定义模块时,要写路径。
模块使用
模块暴露的是一个对象,所以在使用时就当作对象来使用,如:
xxxxxxxxxx
21math.test();
2math.data;
模块定义
1) 在node中一个js文件就是一个模块
2) 在node中每一个模块中的代码都是运行在一个独立的函数中的,
3) 默认情况下模块内部代码对于外部来说都是不可见的,可以通过两种方式向外部暴露变量和函数
a.可以通过将变量和函数设置为
exports
的属性来暴露变量和函数
xxxxxxxxxx
21exports.name = "xxx"; // 追加属性
2exports.sayHello = function(){ }; // 追加属性
b.也可以通过module.exports来向外部暴露变量和函数
xxxxxxxxxx
71module.exports.name = "xxx"; // 追加属性
2module.exports.sayHello = function(){ }; // 追加属性
3module.exports = { // 改变指向
4 a:"xxx",
5 b:"xxx"
6};
7// module.exports = value
模块标识
1) 引入外部模块时,需要通过模块的标识进行引入
2) 对于自定义的文件模块,模块的标识就是文件的路径(绝对路径、以 . 或 .. 开头的相对路径)
例子:"./a
" "../b
"
3) 对于下载的模块或系统模块模块的标识就是文件的名字如"fs
" "express
"
module.exports和exports的区别
exports
变量是对module
的exports
属性的引用,我们在向exports
中添加属性时,本质上是在向module.exports
中添加属性。module.exports 可以直接通过赋值的形式来暴露内容。exports
不能直接赋值,只能通过.的形式添加属性
。
如果是简单的暴露属性,使用
exports
就好,引入模块使用方式:m1.属性名如果需要向外暴露一个构造函数,使用
module.exports
,引入模块使用方式:m1
node中的函数
输入以下代码在node用运行。
xxxxxxxxxx
11console.log(arguments.callee.toString())
会出现:
xxxxxxxxxx
31function (exports, require, module, __filename, __dirname){
2 // js中的内容
3}
这个函数是所有模块都有的,node编译时往其中注入5个参数:
exports
暴露模块
require
引入模块
module
exports属性暴露模块
__filename
文件的绝对路径
__dirname
文件夹的绝对路径
在浏览器端有特殊的目录要求。index页面是必须的。
浏览器不认识require
。需要借助第三方包browserify
。
安装:npm install -g browserify
执行:browserify (输入文件路径) -o (输出文件路径)
,注意路径问题。
浏览器没有引入的先后顺序。
只支持浏览器端。
共有三种暴露方式,分别暴露、统一暴露、默认暴露。
分别暴露:
xxxxxxxxxx
41export let data=1;
2export function demo() {
3 console.log(data)
4}
统一暴露:
x1function demo2() {
2 console.log('我是module2里的demo2函数',arr)
3}
4
5function test2() {
6 console.log('我是module2里的test2函数',arr)
7}
8
9export {demo2,test2}
默认暴露
xxxxxxxxxx
41export default {
2 school:'atguigu',
3 price:'15K'
4}
xxxxxxxxxx
81//引入module1,module1是【分别暴露】的
2import {data,demo1,test1} from './module1' // 不是解构赋值
3//引入module2,module2是【统一暴露】的
4import {demo2,test2} from './module2'
5//【引入module3,module3是默认暴露的】
6import module3 from './module3'
7// 引入第三方,用的是默认暴露
8import uniq from 'uniq'
备注:分别暴露和统一暴露使用频率较少,因为有命名冲突。
x
1// 另外一种写法,收集成对象,默认暴露不推荐这么引入
2import * as haha from './module1'
3
4
5//统一暴露(完整版写法)
6export {
7 demo2 as haha1, //暴露的同时可以,赋一个别名
8 test2 as haha2
9}
备注:暴露可以混着用,引入时默认暴露必须排在前面。
xxxxxxxxxx
21//引入module4,module4里用了多种暴露的方式
2import module4,{arr0,bar,foo,str,student,d1} from './module4'
浏览器不认识ES6的模块化语法。需要一个第三方包Babel
。可以将ES6语法转成ES5语法,并且将jsx
语法转为js
语法。
全局安装:npm install babel-cli browserify -g
局部安装:npm install babel-preset-es2015 -D
创建.babelrc
文件:在根目录下创建,内容如下:
xxxxxxxxxx
31{
2"presets": ["es2015"]
3}
命令:babel (输入文件夹路径) -d (输出文件夹路径)
browserify (输入文件路径) -o (输出文件路径)
备注:成功后还会自动启动严格模式。
全称Asynchronous Module Definition(异步模块定义)。
专门用于浏览器端,模块的加载是异步的。
需要require.js
文件。https://requirejs.org/
基本语法:
xxxxxxxxxx
1/*
2* 定义一个没有依赖的module1
3* */
4
5define(function () {
6 //数据-----私有数据(只读)
7 let data = 'atguigu'
8 //获取数据的方法
9 function getDataL() {
10 return data.toLowerCase()
11 }
12 function getDataU() {
13 return data.toUpperCase()
14 }
15 // 对象简写方式
16 return {getDataL,getDataU}
17})
141/*
2* 定义一个有依赖的module2,module2依赖于module1,要使用module1中的数据--data
3* */
4
5define(['module1'],function (module1) {
6 let msg = '0719就业顺利!'
7
8 //获取module1中的data和module2中的msg
9 function getDataAndMsg() {
10 return module1.getDataL() + msg
11 }
12
13 return getDataAndMsg
14})
x
1/*
2* 在AMD模块化中,主js文件(入口文件)需要写一个特殊的【配置对象】。
3* */
4requirejs.config({
5 baseUrl: './js', //如果配置了baseUrl,项目的根目录了就是index.html所在目录
6
7 //在项目中所有用到的模块,都要在这里注册
8 paths: {
9 module1: '/modules/module1',
10 module2: '/modules/module2',
11 jquery: '/lib/jquery-1.10.1' // 必须是小写
12 }
13});
14
15requirejs(['module2','jquery'],function (m2,$) {
16 console.log(m2());
17 $('body').css('background','skyblue')
18});
html引入:
21<!--AMD模块化中,主页面中也要用特殊的引入方式-->
2<script data-main="./js/app.js" src="./js/lib/require.js"></script>
总结:很老的项目还在用
阿里自己写的模块,全称Common Module Definition()
基本语法:
xxxxxxxxxx
1/*
2* 定义一个没有依赖的模块,module1
3* */
4define(function (require,exports,module) {
5 let data = '--------module1---------'
6 function getData() {
7 console.log(data)
8 }
9 module.exports = {getData}
10})
xxxxxxxxxx
1/*
2* 定义一个没有依赖的模块,module2
3* */
4define(function (require,exports,module) {
5 let data = '--------module2---------'
6 function getData() {
7 console.log(data)
8 }
9 module.exports = {getData}
10})
101/*
2* 定义一个没有依赖的模块,module3
3* */
4define(function (require,exports,module) {
5 let data = '--------module3---------'
6 function getData() {
7 console.log(data)
8 }
9 module.exports = {getData}
10})
x
1/*
2* 定义一个有依赖的模块,module4,依赖于module2和module3
3* */
4define(function (require,exports,module) {
5 let data = '--------module4---------'
6 //引入module2-----同步引入
7 let module2 = require('./module2')
8 module2.getData()
9 //引入module3-----异步引入
10 require.async('./module3',function (m3) {
11 m3.getData()
12 })
13 function getData() {
14 console.log(data)
15 }
16 module.exports = {getData}
17})
汇总:
x
1/*
2* 主js文件,用于汇总各个模块
3* */
4define(function (require) {
5 let module1 = require('./module1')
6 let module4 = require('./module4')
7 module1.getData()
8 module4.getData()
9})
使用sea.js:
xxxxxxxxxx
1<script type="text/javascript" src="js/libs/sea.js"></script>
2<script type="text/javascript">
3 seajs.use('./js/modules/main')
4</script>
输出顺序为:2---1---4---3
Node.js的包基本遵循CommonJS
规范,包将一组相关的模块组合在一起,形成一组完整的工具。
举例:类似电脑上的一个文件夹,包含了某些特定的文件,并且符合某些特定的结构,就是一个包。
包由包结构和包描述文件两个部分组成。
1) 包结构:用于组织包中的各种文件
2) 包描述文件:描述包的相关信息,以供外部读取分析
包实际上就是一个压缩文件,解压以后还原为目录。符合CommonJS规范的目录,应该包含如下文件:
1) package.json
描述文件(必要)
2) bin
可执行二进制文件(可选)
3) lib
js代码(可选)
4) doc
文档(可选)
5) test
单元测试(可选)
包描述文件用于表达非代码相关的信息,它是一个JSON
格式的文件:package.json
包描述文件包含以下字段:name
、version
、description
、keywords
、maintainers
、contributors
、bugs
、licenses
、repositories
、dependencies
(生产依赖)、homepage
、os
、cpu
、engine
、builtin
、directories
、implements
、scripts
、author
、bin
、main
、devDependencies
(开发依赖)。
输入命令:npm init
包名要求:不能有中文、大写字母、尽量不能与其它包同名。
全称:Node Package Manager
, Node的包管理器。
通过NPM可以对Node的包进行搜索、下载、安装、删除、上传。
搜索:npm search xxxx
;或者www.npmjs.com
安装:npm install xxxx -g
(全局安装)== 等同于npm i xxxx -g
npm install xxxx -D
(安装包并写入到开发依赖中)
注意开发依赖和生产依赖的区别:写代码时才用到的库,就是开发依赖;项目上线给其它人用时的库,就是生产依赖,如jquery等等。
npm i xxx@x.x.x
(安装x.x.x版本的包)
npm i
(安装package.json中声明的所有包)
移除:npm remove xxx
(在node_module中删除xxx包,同时删除package.json声明)
其它:npm view xxxx versions
(查看npm仓库中xxx包的所有版本信息)
npm ls xxxx
(查看当前安装的xxx包的版本)
它是淘宝对国外npm服务器的一个完整镜像版本,也就是淘宝 NPM 镜像。可以加速下载。
第一种
直接安装:安装淘宝提供的cnpm,并更改服务器地址为淘宝的国内地址。
npm install -g cnpm --registry=https://registry.npm.taobao.org
,以后安装可以直接采用cnpm代替npm。
缺点:会造成npm和cnpm两种指令共存,会有bug。
第二种
替换npm仓库地址为淘宝镜像地址(推荐)。
命令:npm config set registry https://registry.npm.taobao.org
,查看是否成功:npm config get registry
。
yarn
是Facebook开源的新的包管理器,可以用来代替npm
。
有缓存;没有自己的仓库地址,使用的是npm仓库地址。
npm install yarn -g
在CMD命令行中执行
xxxxxxxxxx
51#1.改变 yarn 全局安装位置
2yarn config set global-folder "你的磁盘路径"
3#2.然后你会在你的用户目录找到 `.yarnrc` 的文件,打开它,找到 `global-folder` ,改为 `--global-folder`
4#这里是我的路径
5yarn config set global-folder "D:\Software\yarn\global"
xxxxxxxxxx
41#2. 改变 yarn 缓存位置
2yarn config set cache-folder "你的磁盘路径"
3#这里是我的路径
4yarn config set cache-folder "D:\Software\yarn\cache"
在我们使用全局安装包的时候,会在“D:\Software\yarn\global”下生成 node_modules\bin
目录
我们需要将D:\Software\yarn\global\node_modules\.bin
整个目录添加到系统环境变量中去,否则通过yarn
添加的全局包 在cmd
中是找不到的。
检查当前yarn的bin的位置:yarn global bin
检查当前yarn的全局安装位置:yarn global dir
随后跟配置npm一样配置环境变量。
yarn global add xxxx
(全局下载指定包)
yarn global remove xxxx
(删除全局依赖包)
yarn info xxxx
(查看某个包的信息)
yarn init -y
(初始化项目)
一共有三种创建方式。
xxxxxxxxxx
11let bf=new Buffer(10);
备注:性能特别差。特点是在堆里开辟空间,同时会清理要回收的空间为己用。
xxxxxxxxxx
11let bf=new Buffer.alloc(10);
备注:性能比new Buffer()稍强一点。特点是在堆中开辟一段空间,且这个空间没有被使用过。
xxxxxxxxxx
11let bf=new Buffer.allocUnsafe(10);
备注:性能最好。特点是在堆里随意开辟空间,可能是别人用过的。
问题
为什么输出的不是二进制?
输出的是16进制,但是存储的是二进制,输出的时候会自动转换为16进制
为什么第三种输出的不为空?
在堆里开辟空空间,可能残留着别人用过的数据。
xxxxxxxxxx
21let bf=Buffer.from('hello world!');
2console.log(bf); // <Buffer 68 65 6c 6c 6f 20 77 6f 72 6c 64 21>
输出的16进制,可以用bf.toString()
方法转为字符串。之所以是16进制,是因为存入的不一定是字符串,也可以是媒体文件如视频、音乐等等。
全称为file system
,所谓的文件系统,就是对计算机中的文件进行增删改查等操作。它是一个服务器的基础,在Node中通过fs模块来操作文件系统。
fs模块是Node的核心模块,不需要下载,直接引入即可使用。
xxxxxxxxxx
21// 引入fs模块
2let fs=require('fs');
fs中的大部分方法都为我们提供了两个版本:
同步方法:带sync的方法
同步方法会阻塞程序的执行;同步方法通过返回值返回结果。
异步方法:不带sync的方法
异步方法不会阻塞程序的执行;异步方法都是通过回调函数来返回结果的。
语法:
xxxxxxxxxx
11fs.writeFile(file, data[, options], callback)
file
:要写入的文件路径+文件名+后缀
data
:要写入的数据
options
:配置对象(可选参数)
--encoding
:设置文件的编码方式,默认值: 'utf8'
--mode
:设置文件的操作权限,默认值: 0o666
--flag
:打开文件要执行的操作。 默认值: 'w'。
--signal
:允许中止正在进行的写入文件
callback
:回调函数
err
:错误对象
例子:
xxxxxxxxxx
81let fs=require('fs');
2fs.writeFile('./demo.txt','helloworld!',(err)=>{
3 if(err){
4 console.log('写入失败',err);
5 }else{
6 console.log('写入成功');
7 }
8});
语法:
xxxxxxxxxx
11fs.createWriteStream(path[, options])
path
:要写入文件的路径+文件名+文件后缀
options
;配置对象(可选参数)
--flags
:同上
--encoding
:同上
-fd
:文件统一标识符,Linux下文件标识符
--mode
:同上
--autoClose
:自动关闭--文件,默认值:true
--emitClose
:关闭-—-文件,默认值:true
--start
:写入文件的起始位(偏移量)
例子:
xxxxxxxxxx
161let fs = require('fs');
2// 创建一个可写流
3let ws = fs.createWriteStream('./demo.txt');
4// 监测流的状态
5ws.on('open',function () {
6 console.log('可写流打开了');
7})
8ws.on('close',function () {
9 console.log('可写流关闭了');
10})
11// 使用可写流写入数据
12ws.write('helloworld!\n');
13ws.write('welcome\n');
14// 关闭
15//ws.close(); 如果node8版本中,会有问题
16ws.end(); // node8版本中可以正常使用
语法:
xxxxxxxxxx
11fs.readFile(path[, options], callback)
path
:要读取文件的路径+文件名+后缀
options
:配置对象(可选)
callback
:回调
例子:
xxxxxxxxxx
81let fs = require('fs');
2fs.readFile('./demo.txt',function (err,data) {
3 if(err){
4 console.log(err);
5 }else{
6 console.log(data.toString()); // 如果是非文本文件,不能这样写
7 }
8})
简单文件写入和简单文件读取,都是一次性把所有要读取或要写入的内容加到内存中,容易造成内存泄露。
语法:
xxxxxxxxxx
11fs.createReadStream(path[, options])
path
:要读取文件的路径+文件名+后缀
options
:同上
--flags
:同上
--encoding
:同上
--fd
:同上
--mode
:同上
--autoClose
:同上
--emitclose
:同上
--start
:起始偏移量
--end
:结束的偏移量
--highwaterMark
:每次读取数据的大小,默认是64*1024
例子:
xxxxxxxxxx
151let {createReadStream} = require('fs'); // 解构赋值
2// 创建一个可读流
3let rs=createReadStream('./demo.txt');
4// 只要用到了流,就必须监测流的状态
5rs.on('open',function () {
6 console.log('可读流打开了');
7})
8rs.on('close',function () {
9 console.log()
10})
11// 给可写流绑定一个data事件,就会触发可读流自动读取内容
12rs.on('data',function (data) {
13 // Buffer实例的length属性,表示该Buffer实例占用内存空间的大小
14 console.log(data.length); // 输出的是6556,每次读取64KB的内容
15})
边读边写的流失例子:
xxxxxxxxxx
241let {createReadStream,createWriteStream} = require('fs');
2let rs=createReadStream('./demo.txt',{
3 highWaterMark:10*1024*1024
4 // start:60000,
5 // end:120000 可以截取片段,视频图片不适用
6});
7let ws=createWriteStream('./demo1.txt');
8rs.on('open',function () {
9 console.log('可读流打开了');
10})
11rs.on('close',function () {
12 console.log('可读流关闭了');
13 ws.close(); // 必须在这里关闭阀
14})
15ws.on('open',function () {
16 console.log('可写流打开了');
17})
18ws.on('close',function () {
19 console.log('可写流关闭了')
20})
21rs.on('data',function (data) {
22 console.log(data.length);
23 ws.write(data);
24})
数据库(DataBase
)是按照数据结构来组织、存储和管理数据的仓库。
我们的程序都是在内存中运行的,一旦程序运行结束或者计算机断电,程序运行中的数据都会丢失。所以我们就需要将一些程序运行的数据持久化到硬盘之中,以确保数据的安全性。而数据库就是数据持久化的最佳选择。
说白了,数据库就是存储数据的仓库。
代表有:MySQL
(⭐,轻量)、Oracle
(⭐,甲骨文)、DB2
、SQL
Server
(微软,大学上课才用)
特点:关系紧密,都是表
优点:
1、易于维护:都是使用表结构,格式一致
2、使用方便:通用,可用于复杂查询(全是SQL语句)
3、高级查询:可用于一个表以及多个表之间非常复杂的查询
缺点:
1、读写性能比较差,尤其是海量数据的高效率读写
2、有固定的表结构,字段不可随意更改,灵活度稍欠
3、高并发读写需求,传统关系型数据库来说,硬盘I/O是一个很大的瓶颈
举例:
代表有:MongoDB
、Redis
(高速缓存,大数据用)
特点:关系不紧密,有文档,有键值对
优点:
1、格式灵活:存储数据的格式可以是key,value形式
2、速度快:nosql可以内存作为载体,而关系型数据库只能使用硬盘
3、易用:nosql数据库部署简单
缺点:
1、不支持sql(结构化查询语句),学习和使用成本较高
2、不支持事务(ACID特性,所以不能用于支付交易)
3、复杂查询时语句过于繁琐
MongoDB是为快速开发互联网Web应用而设计的数据库系统。
MongoDB的设计目标是极简、灵活、作为Web应用栈的一部分。
MongoDB的数据模型是面向文档的,所谓文档是一种类似于JSON的结构,简单理解MongoDB这个数据库中存的是各种各样的JSON。(BSON)
最新社区版:https://www.mongodb.com/try/download/community
教程:https://www.jianshu.com/p/e963d0e0bb94
或者https://blog.csdn.net/yuxiaohe1/article/details/111317947
4.4以上已经自动配置好开机自动启动了,无需另外配置
studio3t:https://studio3t.com/download/(需要破解)
Navicat Premium 15:http://www.navicat.com.cn/download/navicat-premium(需要破解,破解教程https://mp.weixin.qq.com/s/g2myLey2Of0V0_YqwvnWpQ)
https://www.jb51.net/article/206956.htm
在导航栏处的查询按钮可以使用编辑器,有代码高亮和提示,支持多行语句运行。
查看->显示隐藏项目 可以显示被隐藏的数据库
数据库(database
):数据库是一个仓库,在仓库中可以存放集合。
集合(collection
):集合类似于JS中的数组,在集合中可以存放文档。说白了,集合就是一组文档。非常类似于JSON
,因此称为BSON
。
文档(document
):文档数据库中的最小单位,我们存储和操作的内容都是文档。类似于JS中的对象,在MongoDB中每一条数据都是一个文档。
show dbs
或show databases
(注意没有集合的数据库不会显示)use 数据库名
(没有的话会自动创建)db
db.dropDatabase()
show collections
db.collection.drop()
在
MongoDB
,数据库和集合都不需要创建,当我们向集合或数据库中第一次插入文档时,集合和数据库会自动创建。
db.<collection>.insert(doc)
如:db.stus.insert({name:"sunwukong",age:18})
db.<collection>.find()
如:db.stus.find()
CRUD指的是数据库的增删改查(create、delete、update、read)。
查询网址: https://docs.mongodb.com/manual/reference/method/js-collection/
-C create(新增数据):
db.集合名.insert(文档对象)
db.集合名.insertOne(文档对象)
db.集合名.insertMany([文档对象,文档对象])
如:
xxxxxxxxxx
31db.students.insert({name:'zhangsam',age:18,grade:66,sex:'男'});
2db.students.insertOne({name:'lisi',age:20,grade:50,sex:'女'});
3db.students.insertMany([{name:'wangwu',age:19,grade:80,sex:'男'},{name:'xsan',age:17,grade:30,sex:'女'}]);
insert
和insertOne
的底层代码是一样的
-R read(查询数据):
db.集合名.find(查询条件[,投影])
举例:
db.students.find({age:18})
,查找年龄为18的所有信息类例
db.students.find({age:18,name:'jack"})
,查找年龄为18且名字为jack的学生
投影:过滤掉不想要的数据,只保留想要展示的数据。
_id
比较特殊,要单独写。
db.students.find({},{_id:0,name:0})
,过滤掉id和name
db.students.find({},{age:1})
,只保留age
补充:db.集合名.findOne(查询条件[,投影])
,默认只要找到到一个,提高效率。
常用操作符:
$lt $lte $gt $gte $ne
举例:db.集合名.find({age:{$gte:20}})
,年龄是大于等于20的
in
或or
查找年龄为18或20的学生
举例:db.students.find({age:($in:[18,20]}})
举例:db.students.find(($or:[{age:18},{age:20}]})
$nin
举例:db.students.find({age:{$nin:[19,17]}})
,不是19也不是17
举例:db.students.find({name:/^T/})
,查找所有T开头的名字
xxxxxxxxxx
31db.students.find({$where:function(){
2 return this.name === 'zhangsam' && this.age === 18
3}})
-U update(更新数据):
db.集合名.update(查询条件,要更新的内容[,配置对象])
// 如下写法会将更新内容替换掉整个文档对象,但_id不受影响,慎用
举例:db.students.update({name:'zhangsan'},{age:19})
// 使用$set
修改指定内容,其他数据不变,不过只能匹配一个
举例:db.students.update({name:'zhangsan"},{$set:{age:19}})
// 修改多个文档对象,匹配多个,把所有匹配到的年龄都替换为19
举例:db.students.update((name:'zhangsan'},{$set:{age:19}},{multi:true})
补充:db.集合名.updateOne(查前条件,要更新的内容(,配置对象])
db.集合名.updateMany(查询条件,要更新的内容[,配置对象])
-D delete(删除数据):
db.集合名.remove(查询条件)
// 删除所有年龄小于等于19的学生
举例:db.students.remove({age:($lte:19}})
Mongoose
是一个对象文档模型(ODM)库,它对Node原生的MongoDB模块进行了进一步的优化封装,并提供了更多的功能。
mongoDB
:一个数据库品牌的名字
mongod
:启动数据库的命令
mongo
:连接数据库的命令
mongoose
:Node平台下,一个知名的用于帮助开发者连接mongoDB的包
Schema
)下载:
yarn global add mongoose
或者npm i mongoose -g
连接:
xxxxxxxxxx
171// 为什么使用mongoose?可以在node平台下,更加简单、高效、安全、稳定的操作mongoDB
2// 当引入第三方库的时候,如果在本文件夹内没有找到node_modules,找外层文件夹,直到根目录
3let mongoose = require('mongoose');
4mongoose.connect('mongodb://localhost:27017/hello1',{
5 useNewUrlParser:true, // 使用一个新的URL解析器
6 useUnifiedTopology:true // 使用统一的新的拓扑结构
7})
8// 绑定数据库连接的监听
9mongoose.connection.on('open',function (err) {
10 if(err){
11 console.log('数据库连接失败!',err);
12 }else{
13 console.log('数据库连接成功!');
14 // 操作数据库
15 console.log('操作数据库')
16 }
17})
-C create(新增数据):
xxxxxxxxxx
621let mongoose = require('mongoose');
2mongoose.set('useCreateIndex',true); // 创建一个新的索引器
3mongoose.connect('mongodb://localhost:27017/hello1', {
4 useNewUrlParser: true, // 使用一个新的URL解析器
5 useUnifiedTopology: true // 使用统一的新的拓扑结构
6})
7mongoose.connection.on('open', function (err) {
8 if (err) {
9 console.log('数据库连接失败!', err);
10 } else {
11 console.log('数据库连接成功!');
12 // 引入模式对象
13 let Schema = mongoose.Schema;
14 // 创建约束对象
15 let studentsRule = new Schema({
16 stu_id: {
17 type: String,
18 required: true,
19 unique: true // 限制唯一
20 },
21 name: {
22 type: String,
23 required: true
24 },
25 age: {
26 type: Number,
27 required: true
28 },
29 sex: {
30 type: String, // 限制性别必须为字符串
31 required: true // 限制为必填项
32 },
33 hobby: [String], // 限制爱好只能为数组,数组中的每一项必须为字符串
34 info: Schema.Types.Mixed, // 接受所有类型
35 date: {
36 type: Date,
37 default: Date.now() // 默认值
38 },
39 enable_flag: {
40 type: String,
41 default: 'Y'
42 }
43 })
44 // 创建模型对象
45 let stuModel = mongoose.model('students', studentsRule) // 用于生成某个集合所对应的模型对象
46 // CRUD
47 stuModel.create({ // 创建文档对象
48 stu_id:'002',
49 name:'张飞',
50 age:18,
51 sex:'男',
52 hobby:['打代码','看电影'],
53 info:'美男子'
54 },function (err,data) {
55 if(!err){
56 console.log(data);
57 }else{
58 console.log(err);
59 }
60 })
61 }
62})
没有数据库时,会自动创建,其中
__v
是mongo自带的。成功后的图如下:
之后记得右键刷新数据库。
-R read(查询数据):
xxxxxxxxxx
71// 查询操作
2// find方法,返回的是一定是一个数组;若查询结果为空,则返回空数组
3stuModel.find({name:'吕布'},function (err,data) {
4 if(!err) console.log(data);
5 else console.log(err);
6})
7// findOne方法,若有结果返回一个对象,反之返回一个null
-U update(更新数据):
xxxxxxxxxx
61// 更新
2// updateOne为匹配一个修改,updateMany为匹配多个修改
3stuModel.updateOne({name:'吕布'},{age:22},function (err,data) {
4 if(!err) console.log(data);
5 else console.log(err);
6})
-D delete(删除数据):
xxxxxxxxxx
51// 删除,也可以deleteOne
2stuModel.deleteMany({age:22},function (err,data) {
3 if(!err) console.log(data);
4 else console.log(err);
5})
投影:
xxxxxxxxxx
51// 投影
2stuModel.findOne({name: '张飞'}, {age: 1, _id: 0}, function (err, data) {
3 if (!err) console.log(data);
4 else console.log(err);
5})
注意:规则执行过一次之后,只会越来越严格,无法变宽;Mongo提供了字符串自动转译功能,但是对于无法转数字的字符串还是会报错的。
app.js:
xxxxxxxxxx
231let mongoose = require('mongoose');
2let db = require('./db');
3let stuModel = require('./studentModel');
4db(function (err) {
5 if (err) console.log(err);
6 else {
7 // 新增操作
8 stuModel.create({ // 创建文档对象
9 stu_id: '003',
10 name: '张飞',
11 age: 18,
12 sex: '男',
13 hobby: ['打代码', '看电影'],
14 info: '美男子'
15 }, function (err, data) {
16 if (!err) {
17 console.log(data);
18 } else {
19 console.log(err);
20 }
21 })
22 }
23})
db.js:
xxxxxxxxxx
221let mongoose = require('mongoose');
2mongoose.set('useCreateIndex', true);
3const DB_NAME = 'hello1'; // 定义常量,方便日后直接修改,是数据库名字
4const PORT = 27017;
5const IP = 'localhost';
6
7function connectMongo(callback) {
8 mongoose.connect(`mongodb://${IP}:${PORT}/${DB_NAME}`, {
9 useNewUrlParser: true, // 使用一个新的URL解析器
10 useUnifiedTopology: true // 使用统一的新的拓扑结构
11 });
12 mongoose.connection.on('open', function (err) {
13 if (err) {
14 console.log('数据库连接失败!', err);
15 callback('connect failed');
16 } else {
17 console.log('数据库连接成功!');
18 callback();
19 }
20 });
21}
22module.exports = connectMongo; // 函数暴露
studentModel.js:
xxxxxxxxxx
321let mongoose = require('mongoose');
2let Schema = mongoose.Schema;
3let studentsRule = new Schema({
4 stu_id: {
5 type: String,
6 required: true,
7 unique: true // 限制唯一
8 },
9 name: {
10 type: String,
11 required: true
12 },
13 age: {
14 type: Number,
15 required: true
16 },
17 sex: {
18 type: String, // 限制性别必须为字符串
19 required: true // 限制为必填项
20 },
21 hobby: [String], // 限制爱好只能为数组,数组中的每一项必须为字符串
22 info: Schema.Types.Mixed, // 接受所有类型
23 date: {
24 type: Date,
25 default: Date.now() // 默认值
26 },
27 enable_flag: {
28 type: String,
29 default: 'Y'
30 }
31})
32module.exports = mongoose.model('students', studentsRule) // 模块暴露,students是集合名字
由于回调函数是在最后执行的,所以不能直接反馈。所以要通过这种回调函数传参、暴露函数的方法来实现模块化,也可以用promise来实现,但还没学习。
xxxxxxxxxx
131// 引入Node内置的http模块
2let http=require('http');
3// 创建服务对象
4let server=http.createServer(function (request,response) {
5 // 设置响应头
6 response.setHeader('content-type','text/html;charset=utf-8')
7 response.end('<h1>hello world!你好世界!<h1>');
8})
9// 指定服务器运行的端口号(绑定端口监听)
10server.listen(3000,function (err) {
11 if(err) console.log(err);
12 else console.log();
13})
xxxxxxxxxx
181let http=require('http');
2/*形如:key=value&key=value...的编码形式称为urlencoded编码形式
3请求地址里携带这种形式的参数,叫做:查询字符串参数
4* */
5// 引入一个新模块
6let qs=require('querystring');
7let server=http.createServer(function (request,response) {
8 // 获取客户端携带过来的urlencoded编码形式的参数
9 let params=request.url.split('?')[1];
10 let objParams=qs.parse(params); // 转对象
11 let {name,age}=objParams; // 解构赋值
12 response.setHeader('content-type','text/html;charset=utf-8')
13 response.end(`<h1>hello world!${name}你好世界!${age}<h1>`);
14})
15server.listen(3000,function (err) {
16 if(err) console.log(err);
17 else console.log();
18})
运行服务器之后,在浏览器输入http://127.0.0.1:3000/?name=zhangsan&age=18,会出现hello world!zhangsan你好世界!18
Express 是一个基于 Node.js 平台的极简、灵活的 web 应用开发框架,它提供一系列强大的特性,帮助你快速创建各种 Web 和移动设备应用。
简单来说Express就是运行在node中的用来搭建服务器的模块。
npm i express -g
xxxxxxxxxx
61const express=require('express');
2const app=express();
3app.listen(3000,function (err) {
4 if(err) console.log(err);
5 else console.log('启动啦');
6})
路由是指如何定义应用的端点(URIs)以及如何响应客户端的请求。
路由是由一个 URI
、HTTP
请求(GET、POST等)和若干个句柄组成的。
我们可以将路由定义为三个部分
第一部分:HTTP请求的方法(get或post)
第二部分:URI路径
第三部分:回调函数
xxxxxxxxxx
251const express=require('express');
2const app=express();
3// (1)配置路由---对请求的url进行分类,服务器根据分类决定交给谁去处理
4// (2)路由可以理解为:一组组key-value的组合,key:请求方式+URL路径,value:回调函数
5// (3)根据路由定义的顺序,依次定义好路由,随后放入一个类似数组的结构。匹配成功不再匹配。
6app.get('/',function (request,response) {
7 console.log(request.query); // 封装好的响应头
8 response.send('我是主页');
9})
10// 一级路由
11app.get('/food',function (request,response) {
12 response.send('我是美食');
13})
14// 二级路由
15app.get('/food/f1',function (request,response) {
16 response.send('我是美食---1');
17})
18app.post('/',function (request,response) {
19 response.send('我是post的主页');
20})
21// 指定服务器运行的端口号
22app.listen(3000,function (err) {
23 if(err) console.log(err);
24 else console.log('启动啦');
25})
可以配合html的form表单来测试:
xxxxxxxxxx
51<form action="http://127.0.0.1:3000/" method="get">
2 <input type="text" name="name">
3 <input type="password" name="age">
4 <input type="submit">
5</form>
Request
对象是路由回调函数中的第一个参数,代表了用户发送给服务器的请求信息。要先引入express
。
通过
Request
对象可以读取用户发送的请求包括URL
地址中的查询字符串中的参数,和post
请求的请求体中的参数。
属性/方法 | 描述 |
---|---|
request.query | 获取查询字符串的参数,拿到的是一个对象 |
request.params | 获取get 请求参数路由的参数,拿到的是一个对象 |
request.body | 获取post 请求体,拿到的是一个对象(要借助一个中间件) |
request.get(xxxx) | 获取请求头中指定key 对应的value |
参数路由有特殊语法:
xxxxxxxxxx
41app.get('/meishi/:id',function (req,res) { // 写作:id,接收所有虚拟路由
2 let {id}=req.params
3 res.send(`我是${id}商家`)
4})
Response
对象是路由回调函数中的第二个参数,代表了服务器发送给用户的响应信息。
通过
Response
对象可以设置响应报文中的各个内容,包括响应头和响应体。
属性/方法 | 描述 |
---|---|
response.send() | 给浏览器做出一个响应 |
response.end() | 给浏览器做出一个响应(不会自动追加响应头) |
response.download() | 告诉浏览器下载一个文件 |
response.sendFile() | 给浏览器发送一个文件 ,必须传递绝对路径,__dirname |
response.redirect() | 重定向到一个新的地址(url) |
response.set(header,value) | 自定义响应头内容 |
response.get() | 获取响应头指定key对应的value ,但拿不到Date |
res.status(code) | 设置响应状态码 ,尽量不要自己设置 |
备注:若有多个响应则以 response.send为主
中间件(Middleware) 是一个函数,它可以访问请求对象(request
), 响应对象(response
), 和 web 应用中处于请求-响应循环流程中的中间件,一般被命名为 next
的变量。
补充:银行系统和安全系统等需要安全功能的场合会大量使用中间件。
1) 执行任何代码。
2) 修改请求和响应对象。
3) 终结请求-响应循环。(让一次请求得到响应)
4) 调用堆栈中的下一个中间件或路由。
1) 应用级(全局)中间件(过滤非法的请求,例如防盗链)
--第一种写法:app.use((request,response,next)=>{})
--第二种写法:使用函数定义
2) 第三方中间件(通过npm
下载的中间件,例如body-parser
)
3) 内置中间件(express
内部封装好的中间件)
4) 路由器中间件 (Router
)
xxxxxxxxxx
211// 使用应用级(全局)中间件----所有请求的第一扇门
2app.use((request,response,next)=>{
3 // response.send('我是中间件给的响应');
4 // 图片防盗链
5 if(request.get('Referer')){ // 有Referer源才会执行
6 // 切割返回的字符串
7 let miniReferer=request.get('Referer').split('/')[2];
8 if(miniReferer==='localhost:63347'){
9 next(); // 放行
10 }else{
11 // 发生了盗链
12 response.sendFile(__dirname+'err.png');
13 }
14 }else{
15 next();
16 }
17})
18
19app.get('/picture', function (request,response) {
20 response.sendFile(__dirname+'demo.jpg');
21})
备注:以上代码要在app.js里。不是来自端口号63347就不给通过。
优化方案:
xxxxxxxxxx
71// 定义函数
2function gruadpic(request, response, next) {
3 // 放上面的ifelse内容
4}
5app.get('/picture' , gruadpic , function (request,response) {
6 response.sendFile(__dirname+'demo.jpg');
7})
好处:这就是中间件的含义。只有请求该地址的时候才会做出判断,避免了很多无用功的判断。
需要下载一个中间件:npm i body-parser -g
xxxxxxxxxx
151const express = require('express');
2// 引入中间件
3const bodyParse=require('body-parser')
4const app = express();
5// 这个中间件规定的必要的语句
6app.use(bodyParse.urlencoded({extended:true}));
7// 一级路由
8app.post('/food', function (request, response) {
9 console.log(request.body);
10 response.send('我是美食');
11})
12app.listen(3000, function (err) {
13 if (err) console.log(err);
14 else console.log('启动啦');
15})
由此就可以用body拿到post请求过来的数据。
不用使用body-parser
,只要加上这一句express内置的方法。就可以实现上面例子的结果。
xxxxxxxxxx
11app.use(express.urlencoded({extended:true}));
暴露静态资源可以把所有该目录下的html页面交出去,比如http://loaalhost:3000/meishi.html,就可以访问public目录下的meishi.html。
xxxxxxxxxx
21// 使用内置中间件去暴露静态资源--一次性把所指定的文件夹内的资源全部交出去
2app.use(express.static(__dirname + '/public'));
校验数据的合法性:(一般是前台和后台同时验证)
1.校验成功 ->去数据库中查找该邮箱是否法册过 法册过:提示用户邮第已被占用。 未注册:写入数据库
2.校验失败 ->提示用户具体哪里输入的不正确
server.js:
xxxxxxxxxx
901const express = require('express');
2const app = express();
3app.use(express.urlencoded({extended: true}));
4app.use(express.static(__dirname + '/public'));
5
6const usersModel = require('./model/usersModel');
7const db = require('./db/db');
8db(() => {
9 // 用于展示登录界面的路由,无其他任何逻辑 ----- UI路由
10 app.get('/login', function (req, res) {
11 res.sendFile(__dirname + '/public/login.html');
12 })
13 app.get('/register', function (req, res) {
14 res.sendFile(__dirname + '/public/register.html');
15 })
16
17 app.post('/register', function (req, res) {
18 const {email, nick_name, password, re_password} = req.body
19 //校验邮件的正则表达式
20 const emailReg = /^[a-zA-Z0-9_]{4,20}@[a-zA-Z0-9]{2,10}\.com$/
21 //校验昵称的正则表达式
22 const nickNameReg = /[\u4e00-\u9fa5]/gm
23 //校验密码的正则表达式
24 const passwordReg = /^[a-zA-Z0-9_@#.+&]{6,20}$/
25 if (!emailReg.test(email)) {
26 res.send('邮箱格式不合法,用户名必须4-20位,主机名必须2-10位');
27 } else if (!nickNameReg.test(nick_name)) {
28 res.send('昵称格式不合法,必须为中文')
29 } else if (!passwordReg.test(password)) {
30 res.send('密码格式不合法,必须6-20')
31 } else if (password !== re_password) {
32 res.send('两次输入密码不一致')
33 } else {
34 //去数据库中查询该邮箱是否注册过
35 usersModel.findOne({email}, function (err, data) { // 注意,email是简写
36 if (data) {
37 //引入计数模块--当达到一个敏感的阈值,触发安全性机制。
38 console.log(`邮箱为${email}的用户注册失败,因为邮箱重复`)
39 res.send('该邮箱已被注册,请更换邮箱')
40 } else {
41 usersModel.create({email, nick_name, password}, function (err) {
42 if (!err) {
43 //如果写入成功了
44 console.log(`邮箱为${email}的用户注册成功`)
45 res.send('注册成功了')
46 } else {
47 //引入报警模块,当达到敏感阈值,触发报警。
48 console.log(err)
49 res.send('您当前的网络状态不稳定,稍后重试')
50 }
51 })
52 }
53 })
54 }
55 })
56 //用于处理用户的登录请求,有很多业务逻辑(校验数据的有效性等) -------- 业务路由
57 app.post('/login',function (req,res) {
58 //1.获取输入
59 const {email,password}=req.body
60 //2.准备正则
61 const emailReg = /^[a-zA-Z0-9_]{4,20}@[a-zA-Z0-9]{2,10}\.com$/
62 const passwordReg = /^[a-zA-Z0-9_@#.+&]{6,20}$/
63 if(!emailReg.test(email)){
64 res.send('邮箱格式不合法,用户名必须4-20位,主机名必须2-10位')
65 }else if(!passwordReg.test(password)){
66 res.send('密码格式不合法,必须6-20')
67 }else{
68 //3.去数据库中查找:
69 usersModel.findOne({email,password},(err,data)=>{
70 if(err){
71 //引入报警模块,当达到敏感阈值,触发报警。
72 console.log(err)
73 res.send('网络不稳定,稍后重试')
74 return
75 }
76 if(data){
77 // 登录就重定向
78 res.redirect('https://www.baidu.com')
79 return
80 }
81 res.send('用户名或密码输入错误!')
82 })
83 }
84 })
85 //绑定端口监听
86 app.listen(3000, function (err) {
87 if (err) console.log(err);
88 else console.log('启动啦');
89 })
90})
login.html:
xxxxxxxxxx
71<form action="http://localhost:3000/register" method="post">
2 邮箱:<input type="email" name="email" id="email"><br><br>
3 昵称:<input type="text" name="nick_name"><br><br>
4 密码:<input type="password" name="password"><br><br>
5 重复密码:<input type="password" name="re_password"><br><br>
6 <input type="submit">
7</form>
register.html:
xxxxxxxxxx
71<form action="http://localhost:3000/register" method="post">
2 邮箱:<input type="email" name="email" id="email"><br><br>
3 昵称:<input type="text" name="nick_name"><br><br>
4 密码:<input type="password" name="password"><br><br>
5 重复密码:<input type="password" name="re_password"><br><br>
6 <input type="submit">
7</form>
后端和前端验证是必要的,因为前端验证可以有办法跳过。
注意业务路由(处理逻辑)和UI路由(展示页面)的区别。
Router 是一个完整的中间件和路由系统,也可以看做是一个小型的app对象。
为了更好的分类管理route(类比于路由器和端口的概念)
使用Router改注册登录页面案例:
UIRouter.js:
xxxxxxxxxx
241/*
2* 专门用于管理展示界面的UI路由
3* */
4
5//引入Router构造函数
6const {Router} = require('express')
7//创建一个Router实例(路由器就是一个小型的app)
8let router = new Router()
9//引入path模块----Node中内置的一个专门用于解决路径问题的库
10let {resolve} = require('path') // 解构赋值
11
12router.get('/login',(req,res)=>{
13 let url = resolve(__dirname,'../public/login.html')
14 res.sendFile(url)
15})
16
17router.get('/register',(req,res)=>{
18 let url = resolve(__dirname,'../public/register.html')
19 res.sendFile(url)
20})
21
22module.exports = function () {
23 return router
24}
loginRegisterRouter.js:
xxxxxxxxxx
221/*
2* 专门用于管理登录、注册的业务路由
3* */
4
5//引入Router构造函数
6const {Router} = require('express')
7//创建一个Router实例(路由器就是一个小型的app)
8let router = new Router()
9//引入模型对象
10let usersModel = require('../model/usersModel')
11
12router.post('/register',(req,res)=>{
13 // 此处省略
14})
15
16router.post('/login',(req,res)=>{
17 // 此处省略
18})
19
20module.exports = function () {
21 return router
22}
server.js:
xxxxxxxxxx
241const express = require('express')
2const app = express()
3//禁止服务器返回X-Powered-By,为了安全
4app.disable('x-powered-by')
5app.use(express.static(__dirname+'/public'))
6const db = require('./db/db')
7app.use(express.urlencoded({extended:true}))
8//引入UI路由器
9const UIRouter = require('./router/UIRouter')
10//引入登录注册路由器
11const loginRegisterRouter = require('./router/loginRegisterRouter')
12db(()=>{
13 //使用UIRouter
14 app.use(UIRouter())
15 //使用loginRegisterRouter
16 app.use(loginRegisterRouter())
17
18 app.listen(3000,(err)=>{
19 if(!err) console.log('服务器启动成功!')
20 else console.log(err)
21 })
22},(err)=>{
23 console.log('数据库连接失败',err)
24})
备注:
- 中间件本质是一个函数,所以在接口暴露的时候必须要暴露一个函数,然后在函数内部返回对象。
- Node的内置path模块专门要来解决路径的问题。
EJS
是一个高效的 JavaScript 模板后端引擎。
简单来说,使用
EJS
模板引擎就能动态渲染数据。art-template是前端模板引擎。
下载:npm i ejs -g
基础使用:
index.js:
xxxxxxxxxx
211const express = require('express')
2const app = express()
3//让你的服务器知道你在用哪一个模板引擎-----配置模板引擎
4app.set('view engine','ejs')
5//让你的服务器知道你的模板在哪个目录下,配置模板目录
6app.set('views','./haha')
7
8//如果在express中基于Node搭建的服务器,使用ejs无需引入。
9app.get('/show',function (request,response) {
10 let personArr = [
11 {name:'peiqi',age:4},
12 {name:'suxi',age:5},
13 {name:'peideluo',age:6}
14 ]
15 response.render('person',{persons:personArr,a:1})
16})
17
18app.listen(3000,function (err) {
19 if (!err) console.log('服务器启动成功了')
20 else console.log(err)
21})
person.ejs:
xxxxxxxxxx
171
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <title>show</title>
6</head>
7<body>
8<%
9for (var i=0; i<persons.length; i++ ){
10 let item = persons[i] %>
11<ul>
12 <li class="name">姓名:<%=item.name%></li>
13 <li>年龄:<%=item.age%></li>
14</ul>
15<%}%>
16</body>
17</html>
ejs
语法:
< % % >
里面能写任意js代码,但是不会向浏览器输出任何东西。< %- % >
能够将后端传递过来对象指定key所对应value渲染的页面< %= % >
能够将后端传递过来对象指定key所对应value不渲染的页面备注:这是前后端不分离的情况,就是前端语句和后端语句杂糅在一起了,非常不友好。日后趋势是前后端分离。
使用ejs
改注册登录页面案例:(只以登录为例子)
登录的业务路由:
xxxxxxxxxx
351router.post('/login',(req,res)=>{
2 //1.获取输入
3 const {email,password} = req.body
4 //2.准备正则
5 const emailReg = /^[a-zA-Z0-9_]{4,20}@[a-zA-Z0-9]{2,10}\.com$/
6 const passwordReg = /^[a-zA-Z0-9_@#.+&]{6,20}$/
7 //3.准备一个用于收集错误的对象
8 const errMsg = {}
9 if(!emailReg.test(email)){
10 errMsg.emailErr = '邮箱格式不合法,用户名必须4-20位,主机名必须2-10位'
11 }
12 if(!passwordReg.test(password)){
13 errMsg.passwordErr = '密码格式不合法,必须6-20'
14 }
15 // 使用JSON.stringify转译成字符串
16 if(JSON.stringify(errMsg) !== '{}'){
17 res.render('login',{errMsg})
18 return
19 }
20 //3.去数据库中查找:
21 usersModel.findOne({email,password},(err,data)=>{
22 if(err){
23 console.log(err)
24 errMsg.networkErr = '网络不稳定,稍后重试'
25 res.render('login',{errMsg})
26 return
27 }
28 if(data){
29 res.redirect('https://www.baidu.com')
30 return
31 }
32 errMsg.loginErr = '用户名或密码输入错误!'
33 res.render('login',{errMsg})
34 })
35})
备注:
JSON.stringify
可以将对象转译成JSON字符串
login.ejs:
xxxxxxxxxx
91<body>
2<span class="err"><%=errMsg.networkErr%></span>
3<span class="err"><%=errMsg.loginErr%></span>
4<form action="http://localhost:3000/login" method="post">
5 邮箱:<input type="email" name="email" value="<%=errMsg.email%>"><span class="err"><%=errMsg.emailErr%></span><br><br>
6 密码:<input type="password" name="password"><span class="err"><%=errMsg.passwordErr%></span><br><br>
7 <input type="submit">
8</form>
9</body>
本质就是一个字符串,里面包含着浏览器和服务器沟通的信息(交互时产生的信息)。
存储的形式以:key-value的形式存储。
浏览器会自动携带该网站的cookie,只要是该网站下的cookie,全部携带。
会话cookie:关闭浏览器后,会话cookie
会自动消失,会话cookie
存储在浏览器运行的那块内存上。
持久化cookie:看过期时间,一旦到了过期时间,自动销毁,存储在用户的硬盘上,备注:如果没有到过期时间,同时用户清理了浏览器的缓存,持久化cookie
也会消失。
cookie
给浏览器。cookie
种类:
会话cookie
:存储在浏览器运行的那块内存上
持久化cookie
:存储在用户的硬盘上cookie
(无法进行干预)cookie
,分析里面的内容,校验cookie
的合法性,根据cookie
里保存的内容,进行具体的业务逻辑。解决http无状态的问题(例子:7天免登录,一般来说不会单独使用cookie
,一般配合后台的session
存储使用)
备注:
cookie
不一定只由服务器生成,前端同样可以生成cookie
,但是前端生成的cookie
几乎没有意义。
xxxxxxxxxx
481let express = require('express')
2let cookieParser = require('cookie-parser')
3
4let app = express()
5app.use(cookieParser()) // 记得写
6
7//demo路由不对cookie进行任何操作
8app.get('/demo',function (req,res) {
9 res.send('我是demo路由给你的反馈,我没有对cookie进行任何的操作')
10})
11
12//会话cookie,关闭浏览器即立刻消失
13//demo1路由,负责给客户端“种”下一个会话cookie
14app.get('/demo1',function (req,res) {
15 //express中给客户端“种”cookie不需要任何的库
16 let obj = {school:'atguigu',subject:'qianduan'}
17 res.cookie('peiqi',JSON.stringify(obj)) //给客户端种下一个会话cookie
18 res.send('我是demo1路由给你的反馈,我给你种下了一个【会话cookie】,你赶紧去浏览器里看看!')
19})
20
21//demo2路由,负责给客户端“种”下一个持久化cookie
22app.get('/demo2',function (req,res) {
23 let obj = {school:'atguigu2',subject:'qianduan2'}
24 res.cookie('peiqi',JSON.stringify(obj),{maxAge:1000 * 60 * 60 *24 *30}) //给客户端种下一个持久化cookie
25 res.send('我是demo2路由给你的反馈,我给你种下了一个【持久化cookie】,你赶紧去浏览器里看看!')
26})
27
28//demo3路由,负责读取客户端所携带过来的cookie
29app.get('/demo3',function (req,res) {
30 //express中读取客户端携带过来的cookie要借助一个中间件,名为:cookie-parser
31 console.log(req.cookies);
32 const {peiqi} = req.cookies
33 let a = JSON.parse(peiqi)
34 console.log(a.school)
35 res.send('我是demo3路由,我读取了你携带过来的cookie,你去服务器控制台看看吧')
36})
37
38//demo4路由,负责告诉客户端删除一个cookie
39app.get('/demo4',function (req,res) {
40 //第一种方法:res.cookie('peiqi','',{maxAge:0})
41 res.clearCookie('peiqi')
42 res.send('兄台,我删除了你所保存的key为peiqi的那个cookie')
43})
44
45app.listen(3000,function (err) {
46 if(err) console.log(err)
47 else console.log('演示cokkie服务器启动成功了')
48})
备注:
express
中读取客户端携带过来的cookie
要借助一个中间件,名为:cookie-parser
利用cookie
完善登录和个人中心页面:
UI路由.js(部分):
xxxxxxxxxx
221const cookieParser = require('cookie-parser')
2router.use(cookieParser())
3//用于展示个人中心界面的路由,无其他任何逻辑 ----- UI路由
4router.get('/user_center',(req,res)=>{
5 const {_id} = req.cookies
6 if(_id){
7 //去数据库中查找是否有此id
8 //查到了--获取该id对应的昵称
9 usersModel.findOne({_id},function (err,data) {
10 if(!err && data){
11 //进入此判断意味着:用户不仅携带了id,而且id有效
12 res.render('userCenter',{nickName:data.nick_name})
13 }else{
14 //进入此处意味着:1.与数据库交互时产生了错误。2.用户非法篡改了cookie
15 res.redirect('http://localhost:3000/login')
16 }
17 })
18 }else{
19 //进入此处意味着:1.用户的cookie过期了。2.用户清理了浏览器缓存。3.用户根本没有登录过,直接访问的个人中心。
20 res.redirect('http://localhost:3000/login')
21 }
22})
业务路由.js(部分):
xxxxxxxxxx
51if(data){
2 res.cookie('_id',data._id,{maxAge:1000*60})
3 res.redirect(`http://localhost:3000/user_center`)
4 return
5}
标准来说,session这个单词指的是会话。
前端通过浏览器去查看cookie
的时候,会发现有些cookie的过期时间是:session
,意味着该cookie是会话cookie
。
后端人员常常把session会话存储简称为:session
存储,或者更简单的称为:session
存在于服务端;存储的是浏览器和服务器之间沟通产生的一些信息。
默认
session
的存储在服务器的内存中,每当一个新客户端发来请求,服务器都会新开辟出一块空间,供session
会话存储使用。
session
会话存储使用cookie
(有时候会返回多个,为了安全),cookie
里包含着,上一步产生会话存储“容器”的编号(id)cookie
,给服务器cookie
中拿到对应的session
的id,去服务器中匹配注意:
cookie
一定会配合session
使用。session
的持久化,防止由于服务器重启,造成session
的丢失。
session
什么时候销毁?
- 服务器没有做
session
的持久化的同时,服务器重启了。
- 给客户端种下的那个用于保存
session
编号的cookie
销毁了,随之服务器保存的session销毁(不管是否做了session的持久化)。- 用户主动在网页上点击了“注销” “退出登录”等等按钮。
用session完善登录和个人中心页面:
server.js(服务器部分):
xxxxxxxxxx
201//如下代码是配置express中操作session
2//引入express-session,用于在express中简化操作session
3const session = require('express-session');
4//引入connect-mongo,用于做session持久化
5const MongoStore = require('connect-mongo')(session);
6
7app.use(session({
8 name: 'peiqi', //返回给客户端cookie的key。
9 secret: 'atguigu', //参与加密的字符串(又称签名)
10 saveUninitialized: false, //是否在存储内容之前创建session会话
11 resave: true ,//是否在每次请求时,强制重新保存session,即使他们没有变化(比较保险)
12 store: new MongoStore({
13 url: 'mongodb://localhost:27017/sessions_container',
14 touchAfter: 24 * 3600 //修改频率(例://在24小时之内只更新一次)
15 }),
16 cookie: {
17 httpOnly: true, // 开启后前端无法通过 JS 操作cookie
18 maxAge: 1000*30 // 设置cookie的过期时间,cookie的key,cookie的value,均不在此处配置。
19 },
20}));
业务逻辑部分.js:
xxxxxxxxxx
201router.post('/login',(req,res)=>{
2 // ...
3 usersModel.findOne({email,password},(err,data)=>{
4 if(err){
5 // ...
6 }
7 if(data){
8 //1.为请求开辟一个session会话存储空间
9 //2.将客户端与服务器沟通产生的数据放入session会话存储空间
10 //3.获取session会话存储空间的id
11 //4.返回给客户端一个cookie,包含着:将上一步的id。
12 req.session._id = data._id.toString()
13 res.redirect(`http://localhost:3000/user_center`)
14 return
15 }
16 // ...
17 })
18
19})
20
UI逻辑部分.js:
xxxxxxxxxx
121router.get('/user_center',(req,res)=>{
2 //1.获取客户端通过cookie携带过来的session编号
3 //2.根据session编号匹配session容器
4 //3.若匹配上:拿到session容器里的数据,去使用
5 //4.若未匹配上:驳回,去登录
6 const {_id} = req.session // req携带过来的是cookie:{key:peiqi,value:经过加密的session编号}
7 if(_id){
8 // ....
9 }else{
10 // ...
11 }
12})
备注:为了操作
session
并且持久化,需要两个中间件。express-session
和connect-mongo
。小知识📌:很多主流网站只要将你的个人页面cookie全部导出,就可以用这个cookie来登录网站。但只要有一方退出,服务器session就会消失。
为了安全性要对数据库的重要信息进行加密,比如用户注册时的密码。
目前常用的数据加密技术有md5
和sha1
。(sha1
比md5
略强)
安装:npm i md5 -g
使用:
xxxxxxxxxx
101// 引入md5加密模块
2let md5 = require('md5')
3// 注册
4usersModel.create({email,nick_name,password:md5(password)},function (err) {
5 // ...
6})
7// 登录
8usersModel.findOne({email,password:md5(password)},(err,data)=>{
9 // ...
10})
安装:npm i sha1 -g
使用:
xxxxxxxxxx
101// 引入sha1 加密模块
2let md5 = require('sha1 ')
3// 注册
4usersModel.create({email,nick_name,password:sha1 (password)},function (err) {
5 // ...
6})
7// 登录
8usersModel.findOne({email,password:sha1 (password)},(err,data)=>{
9 // ...
10})