-5 前后端交互

前后端交互

  1. node.js

1.1 node.js

1.Node是什么?
Node 是一个基于Chrome V8引擎的Javascript 代码运行环境
Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine.
Node.js是由ECMAScript及Node环境提供的一些附加API组成的,包括文件、网络、路径等更强大的API

2.安装
node -v

3.执行js文件
node helloworld.js 

4.Node.js模块化开发
-模块化开发: 一个功能就是一个模块,多和模块可以组成完整的应用,抽离一个模块不会影响其他功能的运行。
-模块内部可以使用exports对象进行成员导出,使用require方法导入其他模块


-模块成员导出
- exports是module.exports的别名(地址引用关系),导出对象最终以module.exports为准
//a.js
//在模块内部定义变量
let version = 1.0;
//在模块内部定义方法
const sayHi = name =>`hello ,$(name)`;
//向模块外导出数据
export.version = version;
export.sayHi = sayHi;

-模块成员导入
//b.js
//在b.js模块中导入模块a
let a = require('./b.js');
//输出b模块中的version变量
console.log(a.version);
//调用b模块中的sayHi方法并输出其返回值
console.log(a.syaHi('JavaScript'));

5.系统模块
-Node运行环境提供的API为系统模块

---fs 文件操作模块
const fs = require('fs');
fs.reaFile('文件路径/文件名称'[,'文件编码'], callback);
fs.writeFile('文件路径/文件名称', '数据', callback);

//实例,读取上一级css目录下的base.css
fs.readFile('../css/base.css', 'utf-8' (err,doc) => {
    //如果文件读取发生错误,参数err的值为错误对象,否则err的值为null
    //doc参数为文件内容
    if(err == null){
        //在控制台中输出文件内容
        console.log(doc);
    }
})

//实例,写入文件内容
const content = '<h3>正在使用fs.writeFile写入文件内容</h3>';
fs.writeFile('../index.html', content, err => {
    if (err != null) {
        console.log(err);
        return;
    }
    console.log('文件写入成功');
});

---path 路径操作模块
-不同操作系统的路径分隔符不统一
-大多数情况下使用绝对路径,因为相对路径有时相对的是命令行工具的当前工作目录
-在读取文件或者设置文件路径时都会选择绝对路径
-使用_dirname 获取当前文件所在的绝对路径
path.join('路径',’路径,...)

//导入path模块
const path = require('path');
//路径拼接
let finialPath = path.join('itcast','a','b','c.css');
//输出结果为itcast\a\b\c.css, linux为itcast/a/b/c.css
console.log(finialPath);

// _dirname 当前文件所在的绝对路径
fs.readFile(path.join(_dirname, '01.helloworld.js'),'utf8',(err,doc){
      	console.log(err)
      	console.log(doc)      
      })

6. 第三方模块,又名包
-npmjs.com 第三方模块的存储和分发仓库 
-npm(node package manager): node的第三方模块管理工具

-下载
npm inatall 模块名称   //模块包储存在node_modules文件夹下
-卸载
npm unintall package 模块名称

--nodemon
nodemon是一个命令行工具,用来辅助项目开发
-安装
npm install --global nodemon //nodemon解决频繁修改代码重启服务器
npm install nodemon -g
nodemon app.js  // 使用nodemon开启服务器

 --nrm
nrm (npm registry manager): npm下载地址切换工具,切换到npm.taobao.org
npm install nrm -g
nrm ls //出现一个列表
nrm use taobao

1.2 Gulp基础

1. 第三方模块 Gulp
- 基于node平台开发的前端构建工具
- 可以将机械化操作编写成任务,想要执行时就执行一个命令行命令任务就能自动执行
-可以做什么:
	- 项目上线,HTML、CSS、JS文件压缩合并
	- 语法转换(es6, less)
	- 公共文件抽离
	- 修改文件后浏览器自动刷新

2. 安装和使用
- 下载gulp库文件,并没有全局安装,当前项目使用
npm install gulp 
gulp --version
- 安装Gulp的命令行工具
npm install gulp-cli -g
gulp "项目名称"  //在项目文件中执行

- 在项目根目录下建立 gulpfile.js 文件
- 重构项目的文件夹结构 src目录防止源代码文件,dist目录放置构建后的文件
- 在gulpfile.js 文件夹下编写任务
- 在命令行工具中执行Gulp任务

----gulp-demo
   |- dist
   |- gulpfile.js
   |- src --
		   |- css
		   |- js
		   |- images
		   |- index.html
    
3. 编写任务
- Gulp中提供的API方法
gulp.src() -获取任务要处理的文件
gulp.dest() -输出文件
gulp.task() -建立gulp任务
gulp.watch() -监控文件的变化

//示例
const gulp = require('gulp');
//使用gulp.task()方法建立任务
//参数:任务的名称,任务的回调函数
gulp.task('first', () => {
    //获取要处理的文件
    gulp.src('./src/css/base.css')
    //将处理后的文件输出到dist目录
    .pipe(gulp.dest('./dist/css'));
})

gulp first //执行任务

4. Gulp插件
-插件
gulp-htmlmin  -html文件压缩
gulp-csso  -压缩css
gulp-babel   -Javascript语法转化
gulp-less  -less语法转化
gulp-uglify    -压缩混淆JavaScript
gulp-file-include  -公共文件包含 
browsersync  -浏览器实时同步

-使用插件,html文件中代码的压缩操作
npm install --save gulp-htmlmin
gulp minify

//使用
const gulp = require('gulp');
const htmlmin = require('gulp-htmlmin');
 
gulp.task('minify', () => {
  return gulp.src('src/*.html')  //*.html,所有的html文件
    .pipe(htmlmin({ collapseWhitespace: true }))
    .pipe(gulp.dest('dist'));
});

-使用插件,抽取html文件中的公共代码 
npm install  gulp-file-include

//使用
const fileinclude = require('gulp-file-include');
const gulp = require('gulp');

gulp.task('fileinclude', function() {
  gulp.src(['index.html'])
    
    .pipe(fileinclude({
      prefix: '@@',
      basepath: '@file'
    }))
    .pipe(gulp.dest('./'));
});

5. 构建任务
-一次性调用设置的默认插件
gulp.task('default', ['htmlmin','cssmin','jsmin','copy']);

-gulp defalult

1.3 package.json

1. package.json 文件的作用
项目描述文件,记录了当前项目的信息,例如项目名称,版本,作者,github地址,当前项目依赖那些第三方模块。

-初始化生成文件
npm init -y

-丢失moudle文件夹,恢复
npm install

2. 项目依赖
- 在项目的开发阶段和线上运营阶段,都需要依赖的第三方包,称为项目依赖
- 使用npm install包命令下载的文件会默认被添加到package.json文件的dependencies字段中
- npm install
- npm install --production
{
	"dependencies": {
		"jquery": "^3.3.1"
	}
}

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

4. package-lock.json
-package-lock.json作用:锁定包的版本,确保再次下载时不会因为包版本不同而产生问题,加速下载速度
-生成

1.4 node.js中的模块加载机制

1. 模块查找规则-当模块有路径但没有后缀
require('./find.js');
require('./find');
- require 方法根据模块路径查找模块,如果是完整路径,直接引入模块
- 如果模块后缀省略,先找同名JS文件再找同名JS文件夹
- 如果找到了同名文件夹,找文件夹中的index.js
- 如果文件夹中没有index.js就会去当前文件夹中的package.js文件中查找main选项中的入口文件
- 如果找指定的入口文件不存在或者没有指定入口文件就会报错,模块没有被找到

2. 模块查找规则-当模块没有路径且没有后缀时
require('find');
- Node.js 会假设它是系统模块
- Node.js 会去node_modules文件夹中
- 首先看是否会有该名字的JS文件,再看是否有该名字的文件夹
- 如果是文件夹看里边是否有index.js
- 如果没有index.js查看该文件夹中的 package.json中的main选项确定模块入口文件
- 否则找不到报错

3.  

1.5 服务端基础概念

1. URL 
- URL的组成
传输协议://服务器IP或域名:端口/资源所在位置标识
http://www.baidu.com/news/20181018/19512.html

http: 超文本传输协议
端口:默认80端口

2. 服务端与客户端
本机域名:localhost
本地IP : 127.0.0.1

3. HTTP协议
-它基于客户端服务器架构工作,是客户端和服务器端请求和应答的标准

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

-请求报文方式
get/post

1.6 创建web服务器

1. 创建web服务器

//引用系统模块
const http = require('http');
//创建web服务器
const app = http.createServer();
//当客户端发送请求的时候
app.on('request', (req,res) => {
    //响应
    res.end('<h1>hi,user</h1>');
});
//监听3000端口
app.listen(3000);
console.log('服务器已启动,监听3000端口,请访问localhost:3000')

--访问
localhost:3000

2. 响应不同请求方式

//引用系统模块
const http = require('http');
//创建web服务器
const app = http.createServer();
//当客户端发送请求的时候
app.on('request', (req,res) => {
    //响应不同的请求方式
    if (req.method == 'POST'){
        res.end('post') 
    } else of (req.method == 'GET'){
        res.end('get')
    }
});
//监听3000端口
app.listen(3000);
console.log('服务器已启动,监听3000端口,请访问localhost:3000')

3. 响应不同的URL 
--req.headers   //获取请求报文
--req.url      //获取请求地址
--req.method   //获取请求方法

//引用系统模块
const http = require('http');
//创建web服务器
const app = http.createServer();
//当客户端发送请求的时候
app.on('request', (req,res) => {
    //响应不同的URL
    if(req.url == '/index'){
        res.end('welcome to homepage');
    } else if (req.url == '/list'){
        res.end('welcome to listpage');
    } else {
        res.end('not found');
    }
});
//监听3000端口
app.listen(3000);
console.log('服务器已启动,监听3000端口,请访问localhost:3000')

4. 响应报文
--HTTP状态码
200 请求成功
404 请求资源没有找到
500 服务器端错误
400 客户端请求有语法错误
--内容类型
text/html
text/css
application/javascript
image/jpeg
application/json

//引用系统模块
const http = require('http');
//创建web服务器
const app = http.createServer();
//当客户端发送请求的时候
app.on('request', (req,res) => {

    res.writeHead(200, {
        'content-type':'text/html;charset=utf8'
    });
    
    //响应不同的URL
    if(req.url == '/index' || req.url == '/'){
        res.end('welcome to homepage');
    } else if (req.url == '/list'){
        res.end('<h2>welcome to listpage</h2>');
    } else {
        res.end('not found');
    }
    
    //响应不同的URL
    if(req.url == '/index'){
        res.end('welcome to homepage');
    } else if (req.url == '/list'){
        res.end('welcome to listpage');
    } else {
        res.end('not found');
    }
});
//监听3000端口
app.listen(3000);
console.log('服务器已启动,监听3000端口,请访问localhost:3000')

5. get请求获取参数

//引用系统模块
const http = require('http');
//用于处理url地址
const url = require('url');
//创建web服务器
const app = http.createServer();

//当客户端发送请求的时候
app.on('request', (req,res) => {

    res.writeHead(200, {
        'content-type':'text/html;charset=utf8'
    });
    
    console.log(req.url);
    let params = url.parse(req.url,true).query;  //.parse解析URL地址
    console.log(params.name); //.query将查询的参数解析成对象形式
    console.log(params.age);
    
    //响应不同的URL
    if(req.url == '/index' || req.url == '/'){
        res.end('welcome to homepage');
    } else if (req.url == '/list'){
        res.end('<h2>welcome to listpage</h2>');
    } else {
        res.end('not found');
    }
    
    //响应不同的URL
    if(req.url == '/index'){
        res.end('welcome to homepage');
    } else if (req.url == '/list'){
        res.end('welcome to listpage');
    } else {
        res.end('not found');
    }
});
//监听3000端口
app.listen(3000);
console.log('服务器已启动,监听3000端口,请访问localhost:3000')


//改进代码
//引用系统模块
const http = require('http');
//用于处理url地址
const url = require('url');
//创建web服务器
const app = http.createServer();

//当客户端发送请求的时候
app.on('request', (req,res) => {

    res.writeHead(200, {
        'content-type':'text/html;charset=utf8'
    });
    
    console.log(req.url);
    let { query, pathname } = url.parse(req.url,true);  //.parse解析URL地址
    console.log(pathname.name); //.query将查询的参数解析成对象形式
    console.log(pathname.age);
    
    //响应不同的URL,使用pathname判断,pathname不包含请求参数
    if(pathname == '/index' || pathname == '/'){
        res.end('welcome to homepage');
    } else if (pathname == '/list'){
        res.end('<h2>welcome to listpage</h2>');
    } else {
        res.end('not found');
    }
    
    //响应不同的URL
    if(req.url == '/index'){
        res.end('welcome to homepage');
    } else if (req.url == '/list'){
        res.end('welcome to listpage');
    } else {
        res.end('not found');
    }
});
//监听3000端口
app.listen(3000);
console.log('服务器已启动,监听3000端口,请访问localhost:3000');

6. POST请求获取参数
<form method="post" action="http://localhost:3000">
    <input type="text" name="username">
    <input type="password" name="password">
    <input type="submit">
</form>

//引用系统模块
const http = require('http');
//导入系统模块querystring用于将HTTP参数转换为对象格式
const querystring = require('querystring');
//创建web服务器
const app = http.createServer();

//当客户端发送请求的时候
app.on('request', (req,res) => {
	//post参数通过事件的方式接收,多次传输完成
    let postParams= '';
    //data 当请求参数传递的时候触发d
    req.on('data', params => {
        postParams += params;
    });
    
    //end 当参数传递完成的时候触发end事件
    req.on('end', () => {
        querystring.parse(postParams); //解析字符串成对象格式
        console.log(querystring.parse(postParams));
    });
    
    res.end('OK');
});

1.7 路由与资源

1. 路由
-路由: 是指客户端请求地址与服务器端程序代码的对应关系。即请求什么响应什么

//当客户端发来请求的时候
app.on('request', (req,res) => {
	//获取客户端的请求路径
    let { pathname } = url.parse(req.url);
    if(pathname == '/' || pathname == '/index'){
        res.end('欢迎来到首页');
    } else if (pathname == '/list'){
        res.end('欢迎来到列表页');
    } else {
        res.end('抱歉,页面不存在');
    }
}); 

//app.js 
//引入系统模块http
const http = require('http');
const url = require('url');
const app = http.createServer();

app.on('request', (req, res) => {
    //获取请求方式
    const method = req.method.toLowerCase();
    //获取请求地址
    //url.parse(req.url) 把url地址进行解析,返回url地址的各个部分
    const pathname = url.parse(req.url).pathname;
    
    res.writeHead(200, {
        'content-type': 'text/html;charset=utf8'
    });
    
    //响应 get请求
    if (method == 'get'){
        if (pathname == '/' || pathname == '/index'){
            res.end('欢迎来到首页')
        } else if (pathname == '/list'){
            res.end('欢迎来到列表页')
        } else {
            res.end('您访问的页面不存在')
        }
        
    } else if (method == 'post'){
        
    }
});

app.listen(3000);
console.log('服务器已启动,监听3000端口,请访问localhost:3000');

2. 静态资源
-服务器端不需要处理,可以直接响应给客户端的资源就是静态资源,例如CSS、Javascript、image文件
- http://www.baidu.com/images/logo.png

3. 动态资源
-相同的请求地址不同的响应资源
-http://www.baidu.com/article?id=1
-http://www.baidu.com/article?id=2

//引用系统模块
const http = require('http');
const url = require('url');
const fs = require('fs';)
// 根据当前请求文件的类型去设置文件类型
const mime = require('mime')  //npm install mime
//创建web服务器
const app = http.createServer();
//当客户端发送请求的时候
app.on('request', (req,res) => {
    //获取用户的请求路径
    let pathname = url.parse(req.url).pathname;
    //
    pathname == '/' ? 'default.html' : pathname;
    
    //将用户的请求路径转换为实际的服务器硬盘路径, 'public'为文件夹
    let realPath = path.join(_dirname, 'public' + pathname);
    
     // 根据当前请求文件的类型去设置文件类型,指定返回资源的类型
     let type = mime.getType(realPath);
    
    fs.readFile(realPath, (error,result) => {
        if (error != null){
            //加一个报头,否则返回乱码
            res.writeHead(404, {
                'content-type':'text/html;charset=utf8'
            })
            res.end('文件读取失败');
            return;
        }
        
        res.writeHead(200, {
            'content-type': type;
        })
        
        res.end(result)
    })
    
});
//监听3000端口
app.listen(3000);
console.log('服务器已启动,监听3000端口,请访问localhost:3000')

--访问
localhost:3000

1.8 异步编程

1. 同步/异步 API
-同步API:只有当前API执行完成了,才能继续执行下一个API
console.log('before')
console.log('after')

-异步API:当前的API的执行不会阻塞后续代码的执行

console.log('before')
setTimeout(() => {
    {console.log('last')};
}, 2000);
console.log('after');   //before after last 

2. 同步/异步 API区别 (获取返回值)
-同步API可以从返回值中拿到API执行的结果,但是异步API是不可以的
//同步
function sum(n1,n2){
    return n1 + n2;
}
const return = sum(10, 20);

//异步
function getMsg() {
    setTimeout(function () {
        return (msg: 'Hello Node.js')
    }, 2000);
} 
const msg = getMsg();  //undefined

3. 回调函数
-自己定义函数让别人去调用

//回调函数
function getData (callback) {
    callback('123')
}

getData(function (n) {
    console.log('callback函数被调用了')
    console.log(n) // 123
} )

//异步
function getMsg(callback) {
  
    setTimeout(function () {
       callback({  //调用callback之后,将异步API执行的结果,通过参数的形式传递出来
            msg: 'Hello Node.js'
        })     
    }, 2000);
} 
getMsg(function (data) {
    console.log(data);
});

4. 同步/异步 API区别 (代码执行顺序)
- 同步API从上到下依次执行,前面的代码会阻塞后面代码的执行
- 异步API不会等待API执行完成后再向下执行代码

5.Node.js中的异步API
fs.readFile('./demo.txt', (err,result) => {});

var server = http.createServer();
server.on('request', (req,res) => {});

6. Promise 
-使用Promise解决Node.js异步编程中的回调地狱问题

//实例
let promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        if (true) {
            resolve({name: 'zhangsan'})
        } else {
            reject('fail')
        }
    },2000);
});

promise.then(result => console.log(result));  //{name:'zhangsan'}
		.catch(error => console.log(error));  //fail

// promise 是一个对象,new 一个Promise,在Promise中进行方法操作,
// 成功结果返回给方法函数resolve(res),失败结果返回给方法函数reject(error)
// .then() 中就可以拿到成功的回调函数和失败的回调函数

//异步读取文件
const fs = require('fs');

let promise = new Promise((resolve,reject) => {
    fs.readFile('./1.txt', 'utf8',(err,result) => {
        
        if(err != null){
            reject(err);
        } else {
            resolve(result);
        }
    });
});

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

//异步读取文件,异步调用异步
let p1 = new Promise((resolve, reject) => {
    fs.readFile('./1.txt', 'utf8',(err,result) => {
        resolve(result)
    })
});

let p2 = new Promise((resolve, reject) => {
    fs.readFile('./1.txt', 'utf8',(err,result) => {
        resolve(result)
    })
});

let p3 = new Promise((resolve, reject) => {
    fs.readFile('./1.txt', 'utf8',(err,result) => {
        resolve(result)
    })
});

//依次读取
function p1 (){
    return new Promise((resolve, reject) => {
    fs.readFile('./1.txt', 'utf8',(err,result) => {
        resolve(result)
     })
  });
}

function p2 (){
    return new Promise((resolve, reject) => {
    fs.readFile('./1.txt', 'utf8',(err,result) => {
        resolve(result)
     })
  });
}

function p3 (){
    return new Promise((resolve, reject) => {
    fs.readFile('./1.txt', 'utf8',(err,result) => {
        resolve(result)
     })
  });
}

//异步调用异步
p1().then((r1) => {
    console.log(r1);
    return p2();  //返回的是一个promise对象
}).then((r2) => {
    console.log(r2);
    return p3();
}).then((r3) => {
    console.log(r3);
})


7. 异步函数
-可以将异步代码写成同步的形式,让代码不再有回调函数的嵌套。
const fn = async () => {};
async function fn () {};

-async关键字
-1.普通函数定义前加async关键字,普通函数变成异步函数
-2.异步函数默认返回promise对象
-3.在异步函数内部使用return关键字进行结果返回,结果会被包裹在promise对象中,return关键字代替了resolve方法
-4.在异步函数内部使用throw关键字抛出程序异常
-5.调用异步函数再链式调用then方法获取异步函数执行结果
-6.调用异步函数再链式调用catch方法获取异步函数执行的错误信息

-await关键字
-1.await关键字只能出现在异步函数中
-2.await promise await后面只能写promise对象,写其他的类型API是不可以的
-3.await关键字可以暂停异步函数向下执行,直到promise返回结果

//实例
async function p1() {
    return 'p1';
}

async function p2() {
    return 'p3';
}

async function p3() {
    return 'p3';
}

async function run() {
    let r1 = await p1()
    let r2 = await p2()
    let r3 = await p3()
    console.log(r1)
    console.log(r2)
    console.log(r3)
}

//node中的应用
const fs = require('fs');
//改造现有API,包装异步API使其返回一个Promise对象,从而支持异步函数语法
const promisify = require('util').promisify;
//调用promisify方法改造现有异步API,让其返回promise对象
const readFile = promisify(fs.readFile);

async function run(){
  //await 暂停当前函数run()的执行,等待readFile这个API返回结果,再继续执行函数
    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)  // 1 2 3
}
run();

1.9 全局对象global

1. Node.js全局对象global
-在浏览器中全局对象是window,在Node中全局对象是global
-Node中全局对象有以下方法,global可以省略
console.log()   在控制台输出
setTimeout()	设置超时定时器
clearTimeout()	清除超时定时器	
setinterval()	设置间歇定时器
clearinteval()	清除间歇定时器
  1. MongoDB

4.1 MongoDB

1. 数据库
-动态网站中的数据都是储存在数据库中的
-数据库可以用来持久储存客户端通过表单收集的用户信息
-数据库软件本身可以对数据进行高效的管理
-数据库即储存数据的仓库,可以将数据进行有序的分门别类的储存

2.相关概念
-database 数据库,mongoDB数据库软件中可以建立多个数据库
-collection 集合,一组数据的集合,可以理解为Javascript中的数组
-document  文档,一条具体的数据,可以理解为Javascript中的对象
-field 字段,文档中的属性名称,可以理解为Javascript中的对象属性

3.mongoose
-使用Node.js操作MongoDB数据库需要依赖Node.js第三方包mongoose
-安装
npm install mongoose
mongod --version   //测试是否安装成功

4. 启动MongoDB
--启动
net start mongoDB
net stop mongoDB

mongod 回车  //启动,C盘启动,第一次新建文件夹/data/db
Ctrl + C 即可停止

-简单操作
*另打开一个cmd
mongo   //连接本地mongodDB数据库
exit
show dbs
use 数据库名称  //查看数据库,没有会新建
show collections 


5. 链接数据库
-使用mongoose提供的connect方法即可链接数据库

//数据库名称为playground,没有则自动创建
mongoose.connect('mongodb://localhost/playground')
		.then(() => console.log('数据库连接成功'))
		.catch(err => console.log('数据库连接失败', err));

//实例
const mongoose = require('mongoose');

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

6. 增删改查
-创建集合
-与数据库相关的所有操作都是异步操作
--分为两步,一是对集合设定规则,二是创建集合。创建mongoose.Schema构建函数的实例即可创建集合

//设定集合规则
const courseSchema = new mongoose.Schema({
    name: String,
    author: String,
    isPublished: Boolean
});
//创建集合并应用规则,mongoose创建的集合名称为couses
const Course = mongoose.model('Course', courseSchema);//第一个参数是集合名称


//实例
const mongoose = require('mongoose');

mongoose.connect('mongodb://localhost/playground',{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) //courses


-创建文档
-即向集合中插入数据
-第一,创建集合实例。二,调用实例对象下的save方法将数据保存到数据库中

//使用规则创建集合实例
const course = new Course ({
    name: 'Node.js course',
    author: 'teacher',
    tags: ['node','backed'],
    isPublished: true
});
//将数据保存到数据库中
course.save();

-创建文档的另一种方式
//向集合中插入文档
Course.create({name: 'Javascript',author: 'teacher',isPublished:true},(err,result) =>{
	console.log(err);
	console.log(result);
});

Course.create({name: 'Javascript and C##',author: 'teacher',isPublished:true})
          .then(result => {
          	console.log(result)
          })
          .catch(err => {
          	console.log(err)
          })

7. mongoDB 数据库导入数据
-cmd
mongoimport -d 数据库名称 -c 集合名称 -file 要导入的数据文件
mongoimport -d playground -c users -file ./user.json

8. 查询文档
//根据条件查找文档,(条件为空则查找所有文档)
Course.find().then(result => console.log(result));

//返回文档集合 collection 文档 ,返回的是一个数组
[{
    _id:5e4a1718b187c2324c3b95b7
	name:"Node.js course"
	author:"teacher"
	isPublished:true
},{
    _id:5e4a1718b187c2324c3b95b7
	name:"Node.js course"
	author:"teacher"
	isPublished:true
}]

//根据条件查找文档,返回一条文档,返回的是一个对象,默认返回当前集合中的第一条文档
Course.findOne({name: 'node.js'}).then(result => console.log(result));

//匹配大于,小于
Course.find({age: {$gt:20,$lt: 50}}).then(result => console.log(result));
//匹配包含
Course.find({hobbies: {$in: ['敲代码']}}).then(result => console.log(result));
//选择要查询的字段
Course.find().select('name email').then(result => console.log(result));
//将数据按照年龄进行排序, '-age' 降序排列
Course.find().sort('age').then(result => console.log(result));
Course.find().sort('-age').then(result => console.log(result));  
//skip 跳过多少条数据 limit限制查询数量
Course.find().skip(2).limit(2).then(result => console.log(result));

8. 删除文档
//删除单个
Course.findOneAndDelete({}).then(result => console.log(result))

//查询到一条文档并删除,返回删除的文档
//如果查询体条件匹配了多个文档,将会删除第一个匹配的文档
Course.findOneAndDelete({_id: 'idnumber'}).then(result => console.log(result))

//删除多个
Course.deleteMany({}).then(result => console.log(result))

//删除所有,返回值为一个对象,删除信息
Course.deleteMany({}).then(result => console.log(result))


9. 更新文档
//更新单个
User.updateOne({查询条件},{要修改的值}).then(result => console.log(result))
//更新单个的name
User.updateOne({name: 'name1'},{name: 'name2'}).then(result => console.log(result))

//更新多个
User.updateMany({查询条件},{要修改的值}).then(result => console.log(result))

10. mongoose验证
-在创建集合规则时,可以设置当前字段的验证规则,验证失败就输入插入失败
-type 类型
-required: true 必传字段
-minlength: 2  针对字符串类型
-maxlength: 5
-trim:true 去除空格
-min: 2  数值最小为2,针对数值类型
-max: 100  数值最大为100
-default: 默认值
-enum:['',''] 列举出当前字段可以拥有的值
-validate 自定义验证规则,自定义验证器

new mongoose.Schema({
    title: {
        type: String,
        required: [true, '请传入文章标题'],  //如果错误,会弹出提示
        minlength: [2, '文章长度不能小于2'],
        maxlength: [5, '文章长度最大不能超过5']
        publishDate: {
       		type: Date,
        	default: Date.now 
          }
    	category: {
    		type: String,
    		//枚举, 列举出当前字段可以拥有的值
    		enum: ['html','css','javascript','node.js']
		  }
		category: {
    		type: String,
    		//枚举, 列举出当前字段可以拥有的值
    		enum: {
                 values:  ['html','css','javascript','node.js'],
                 message: '分类名称要在一定范围内才可以'   //自定义错误提示信息
            }
		  }	
		validate: {
            validator: v =>{
                //返回布尔值,true验证成功,false 验证失败,v 为要验证的值
                return v && v.length > 4 
            },
            // 自定义错误信息
            message: '传入的值不符合验证规则'
        }
    }
});

//获取错误信息实例
const Post = mongoose.model('Post',postSchema);

Post.create({title: 'aa'}).then(result => console.log(result))
	.catch(error => {
    //获取错误信息对象
    const err = error.errors;
    //循环错误信息对象
    for(var attr in err){
        //将错误信息打印到控制台中
        console.log(err[attr]['message']);
    }
})


11. 集合关联
-通常不同集合的数据之间是有关系的
-使用id对集合进行关联
-使用populate方法进行关联集合查询

//用户集合
const User = mongoose.model('User', new mongoose.Schema({ name: {type:String}}));

//文章集合
const Post = mongoose.model('Post', new mongoose.Schema({
    title: { type: String },
    //使用ID将文章集合和作者集合进行关联
    author: { type: mongoose.Schema.Types.ObjectId, ref: 'User'}
}));
//联合查询
Post.find()
	.populate('author')
	.then((err,result) => console.log(result));

//用户集合
const User = mongoose.model('User', userSchema);
//文章集合
const Post = mongoose.model('Post', postSchema);
//创建用户
User.create({name: 'itname'}).then(result => console.log(result));
//创建文章 
Post.create({title: '123', author: '5e4a1af25589623280b2ec11'})
    .then(result => console.log(result))
//查询
Post.find().populate('author').then(result => console.log(result));

4.2 实例代码

const mongoose = require('mongoose');

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

//创建集合规则 .Schema
const courseSchema =  new mongoose.Schema({
    name: String,
    author: String,
    isPublished: Boolean
})
//使用规则创建集合 .model
//集合名称 集合规则
const Course = mongoose.model('Course',courseSchema) //courses

// //使用规则创建集合的文档实例
// const course = new Course ({
//     name: 'Node.js course',
//     author: 'teacher',
//     tags: ['node','backed'],
//     isPublished: true
// });

// //将创建的文档保存到数据库中
// course.save(); 

//创建集合的另一种方法
//向集合中插入文档
Course.create({name: 'Javascript and C##',author: 'teacher',isPublished:true})
          .then(result => {
          	console.log(result)
          })
          .catch(err => {
          	console.log(err)
          })

//增删改查综合案例

* 1. 搭建网站服务器,实现客户端与服务器端的通信
* 2. 连接数据库,创建用户集合,向集合中插入文档
* 3. 当用户访问 /list时,将所有的用户信息查询出来
* 4. 将用户信息和表格HTML进行拼接并将拼接的结果响应回客户端
* 5. 当用户访问 /add时,呈现表单页面,并实现添加用户信息功能
* 6. 当用户访问 /modify时,呈现修改页面,并实现修改用户信息功能
      1. 增加页面路由,呈现页面
      		1. 在点击修改按钮的时候,将用户的ID传递到当前页面
            2. 从数据库中查询当前用户信息,将用户信息展示到页面 
      2. 实现用户修改功能
      		1. 指定表单的提交地址以及请求方式
            2. 接受客户端传递过来的修改信息,找到用户信息更改为最新信息
* 7. 当用户访问 /delete时,实现用户删除功能


---user
   |- app.js
   |- user ----|--add.html
   |-		   |--list.html

//list.html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>用户列表</title>
    <link rel="stylesheet" href="css/test.css"></link>
       <!-- Bootstrap -->
    <link href="bootstrap/bootstrap-3.3.7-dist/css/bootstrap-theme.min.css" rel="stylesheet">
</head>

<body>
   <div class="container">
       <h6>
           //<a href="add.html" class="btn btn-primary">添加用户</a>
           <a href="/add" class="btn btn-primary">添加用户</a>
       </h6>
       <table class="table table-striped table-bordered">
            <tr>
                <td>用户名</td>
                <td>年龄</td>
                <td>爱好</td>
                <td>邮箱</td>
                <td>操作</td>
            </tr>
			//list.html的前半部
            <tr>
                <td>张三</td>
                <td>20</td>
                <td>
                    <span>爱好1</span>
                    <span>爱好2</span>
                    <span>爱好3</span>
                </td>
                <td>zhangsan@baidu.com</td>
                <td>
                    <a href="" class="btn btn-danger btn-xs">删除</a>
                    <a href="" class="btn btn-danger btn-xs">修改</a>
                </td>
            </tr>
			//list.html的后半部
       </table>
   </div>

</body>
</html>


//app.js
const http = require('http');
const mongoose = require('mongoose'); //第三方模块,安装
const url = require('url');
const querystring = require('querystring');

//2.0数据库连接, 需要启动数据库
mongoose.connect('mongodb://localhost:27017/playground',{useNewUrlParser: true})
		.then(() => console.log('数据库连接成功'))
		.catch(() => console.log('数据库连接失败'))

//2.1创建用户集合规则
const userSchema = new mongoose.Schema({
		name: {
			type: String,
			required: true,
			minlength: 2,
			maxlength: 20
		},
		age: {
			type: Number,
			min: 18,
			max: 80,
		},
		password: String,
		email: String,
		hobbies: [ String ]  //是一个数组,数组中每一个应该为字符串

})	

//2.2创建集合 返回集合构造函数
const User = mongoose.model('User',userSchema);

//2.3插入数据,在cmd操作
mongoimport -d playground -c users -file ./user.json

//1.0创建服务器
const app = http.createServer();

//1.2为服务器对象添加请求事件
app.on('request', async (req, res) => {

	//3.0获取请求方式
	const method = req.method;
	//3.1 获取请求地址 query保存了get请求的参数
	const { pathname, query } = url.parse(req.url, true);

	if (method == 'GET'){
		// 3.3呈现用户列表页面
		// 3.3呈现用户列表页面
		if (pathname == '/list '){
			//查询用户信息
			 let users = await User.find();
			 //console.log(users);
			 //html字符串
			let list = `
				list.html的前半部
			`;

			//在循环数据中添加数据
			users.forEach(item => {
				list += `
				<tr>
                <td>${item.name}</td>
                <td>${item.age}</td>
				`
			})
			//再次循环hobbies
			item.hobbies.forEach(item => {
				list += `<span>${item}</span>`
			})

			list += `
			</td>
                <td>${item.email}</td>
                <td>
                    <a href="" class="btn btn-danger btn-xs">删除</a>
                    //5.1在点击修改按钮的时候,将用户的ID传递到当前页面
                    <a href="/modify?=${item_id}" class="btn btn-danger btn-xs">修改</a>
                </td>
            </tr>`

			list += `
				list.html的后半部
			`;

			res.end(list);
		} else if(pathname == '/add'){
			
			let add= `add.html的代码`

			res.end(add);
		} else if (pathname == '/modify'){
			//5.2使用query参数获取id
		    let user = await User.findOne({_id: query.id})

			//5.3修改和添加跳转的是同一个页面,只修改按钮文字,保存改为修改
			//使用类似于list.html
			let modify= `add.html的代码
			$(user.name)
			$(user.age)
			$(user.email)
			`
			hobbies.forEach(item => {
				//5.4判断当前循环项在不在用户的爱好数据组
				let isHobby = user.hobbies.includes(item);
				if (isHobby){
					modify += `
					<label>
						<input type="checkbox" value="${item}" name="hobbies" checked>${item}
					</label>
					`
				} else {
					modify += `
					<label>
						<input type="checkbox" value="${item}" name="hobbies" >${item}
					</label>
					`
				}
			})
			res.end(modify);
		} else if (pathname == '/remove') {
			await User.findOneAndDeltte({_id: query.id});
			res.writeHead(301,{
				LOcation: '/list'
			});
			res.end();
		}


	}else if {method == "POST"}{
		//4.0用户添加功能
		if (pathname == '/add'){
		    
		    // 4.1接收用户提交的信息
		    let formData= '';
		    //4.2接收post参数
		    req.on('data', param=> {
		    	formData += param;
		    })
		    //4.3post参数接收完毕
		    req.on('end', async()=> {
		    //4.4使用querystring解析post参数
		    let user = querystring.parse(formData)
		    //4.5将用户提交的信息添加到数据库中
		    await User.create(user);
		    //301代表重定向
		    //location 代表跳转地址
		    res.writeHead(301, {
		    	Location: '/list'
		       });

		    res.end();
		    })
		    //6.0 接收修改后提交的表单信息
		} else if (pathname == '/modify'){
			// 6.1接收用户提交的信息
		    let formData= '';
		    //6.2接收post参数
		    req.on('data', param=> {
		    	formData += param;
		    })
		    //6.3post参数接收完毕
		    req.on('end', async()=> {
		    //6.4使用querystring解析post参数
		    let user = querystring.parse(formData)
		    //6.5将用户提交的修改后的信息添加到数据库中
		    //<form method="post" action="/modify?id=${user._id}">
		    await User.updateOne({_id: query.id}, user);
		    //301代表重定向
		    //location 代表跳转地址
		    res.writeHead(301, {
		    	Location: '/list'
		       });

		    res.end();
		} 
	}

	res.end('ok')
}

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


//app.js改进
1. 模块化分割功能文件
>>>model文件夹
      >index.js 放置连接数据库的代码
      >user.js  放置创建用户集合的代码
>>>app.js 
    require('./model/index.js');
    const User = require('./model/user.js');

2. 拼接HTML代码                
模板引擎改进        

4.3 模板引擎

1.模板引擎
-模板引擎是第三方模块,可以更优雅的拼接字符串,将html和数据拼接

///实例
<ul>
    {{each ary}}
    	<li>{{$value.name}}</li>
    	<li>{{$value.age}}</li>
	{{/each}}
<ul>

2.art-template模板引擎
-安装并引入
npm install art-template 
const template = require('art-template')
const html = template('模板路径', 数据);

//实例
//导入模板引擎模块
const template = require('art-template');
//将特定模板与特定数据进行拼接
const html = template('./views/index.art',{
    data: {
        name: 'zhangsan',
        age: 20
    }
});

<div>
    <span>{{data.name}}</span>
    <span>{{data.age}}</span>
<div>      
      
3.实例
>>>template 文件夹
		>>>views 文件夹
		  >index.art
        >>>app.js
      
//app.js      
//npm install art-template 
const template = require('art-template');
const path = require('path');

//告知template拼接那一个模板和哪一个数据
//告知template如何拼接那个模板和数据

//template方法是用来拼接字符串的
// 1. 模板路径 绝对路径
const views = path.join(_dirname,'views','index.art')
//2. 要在模板中显示的数据,对象类型
//返回拼接好的字符串
const html = template(views,{
	name: 'zhangsan',
	age: 20
});
console.log(html)      
      
//index.art      
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>用户列表</title>    
</head>
<body>
   {{name}}
   {{age}}
</body>
</html>      
      
4.模板引擎语法      
-art-template同时支持两种模板语法,标准语法和原始语法

5.输出
-标准语法: {{ 数据 }}
-原始语法: <%=数据 %>
    
-标准语法
<h2>{{value}}</h2>
<h2>{{a ? b : c}}</h2>
<h2>{{a + b}}</h2>

-原始语法
<h2><%= value %></h2>    
<h2><%= a ? b : c %></h2>    
<h2><%= a + b %></h2>    
     
6.原文输出
-如果数据中携带HTML标签,默认模板引擎不会解析标签,使用@进行原文输出
标准语法: {{ @数据 }}
原始语法: <%-数据 %>    
    
7.条件判断
-在模板中可以根据条件来决定显示那块HTML代码
//标准语法
{{if 条件}} ... {{/if}}
{{if v1}}  ...  {{else if v2}}  ... {{/if}}    
//原始语法
<% if (value) { %> ... <% } %>                              
<% if (v1) { %> ... <% } else if (v2) { %> ... <% } %>                                     
                                     
//index.art
{{if age > 18}}
年龄大于18
{{else if age < 15}}
年龄小于15
{{else}}
年龄不符合要求
{{/if}}                                     
                                     
8.循环
-标准语法: {{each 数据}}{{/each}}
-原始语法: <% for() { %> <%} %>                    
    
//语法
{{each target}}
    {{$index}} {{$value}}
{{/each}}    
    
<% for(var i = 0; i < target.length;i++){ %>
  <%= i %> <%= target[i] %>
<%}%>    
  
//实例
//index.art
<ul>
    {{each users}}
  		<li>
            {{$value.name}}
            {{$value.age}}
            {{$value.sex}}
  		<li>
  	{{/each}}
<ul>
  
9.子模版
-使用子模版可以将网站公共区域(头部,底部)抽离到单独的文件夹中
-标准语法 {{include '模板'}}
-原始语法 <%include('模板')%>
 
//语法
{{include './header.art'}}
<% include('./header.art') %>                    
  
//实例
//index.art
{{ include './common/header.art' }}    
<div>{{ msg }}</div>    
{{ include './common/footer.art' }} 
   
//>>>common >header.art
我是头部                   
                   
//>>>common >footer.art
我是底部       
  
10.模板继承  
-使用模板继承可以将网站的HTML骨架抽离到单独的文件中,其他页面模板可以继承骨架文件
-{{block 'name'}}  

HTML骨架模板  
    |
    |继承 extend
    |                  包含子模版 block
首页页面模板/其他页面模板 ——————————------> 头部模板
    
//实例
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>用户列表</title>   
    {{block 'head'}}{{/block}}
</head>
<body>
   {{block 'content'}}{{/block}}
</body>
</html> 
    
//index.art
{{extend './layout.art'}}                        
{{block 'head'}}<link rel="stylesheet" href="custom.css"> {{/block}}                       
{{block 'content'}}<p>This is just an awesome page.</p>{{/block}}   
 
11.模板配置
- 向模板中导入变量,template.defaults.imports.变量名 = 变量值
- 设置模板根目录 template.defaults.root = 模板目录
- 设置模板默认后缀 template.defaults.extname = '.art' 
        
-安装及引入
npm install dateformat 
var dateFormat = require("dateformat");

//实例
//06.art
 {{ dateFormat(time, 'yyyy-mm-dd') }}
 
//06.js
const template = require('art-template');
const path = require('path'); 
const dateFormat = require('dateformat'); 
 
const views = path.join(_dirname, 'views', '06.art');
//导入模板变量
template.defaults.imports.dateFormat = dateFormat;
 
const html = template(views, {
    time: new Date()
});

console.log(html); 
 
//批量模板导入实例
//06.art
 {{ dateFormat(time, 'yyyy-mm-dd') }}
 
//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 = '.art';
 //或者配置模板的默认后缀
template.defaults.extname = '.html';
 
const html = template('06.art', {
    time: new Date()
});
console.log(html);  

12.第三方路由模块router
-功能:实现路由
-步骤: 获取路由对象,调用路由对象提供的方法创建路由,启用路由使生效
-安装
npm install router

//实例
const getRouter = require('router')
const router = getRouter();
router.get('/add',(req,res) => {
    res.end('Hello World!')
})
server.on('request',(req,res) => {
    router(req,res)
}) 

 
13.三方模块 serve-static
 -功能:实现静态资源访问服务
 步骤: 1.引入serve-staic模块获取创建的静态资源服务功能
 		2. 调用方法创建静态资源服务,并指定静态资源服务目录
        3. 启用静态资源服务功能
-安装
npm install serve-static

//实例
const serveStatic = require('serve-static')
const serve = serveStatic('public')
server.on('request',() => {
    serve(req, res)
})

server.listen(3000)

4.4 案例代码

--案例:学生档案管理
1.建立项目文件夹并生成项目描述文件
2.创建网站服务器实现客户端和服务器端通信
3.连接数据库并根据需求设计学员信息表
4.创建路由并实现页面模板呈递
5.实现静态资源访问
6.实现学生信息添加功能
	1. 在模板的表单中指定请求地址与请求方式
    2. 为每一个表单项添加name属性
    3. 添加实现学生信息功能路由
    4. 接收客户端传递过来的学生信息
    5. 将学生信息添加到数据库中
    6. 将页面重定向到学生信息列表页面
7.实现学生信息展示功能
	1. 

>>>students 文件结构
		 >app.js  --项目的入口文件
		 >>>route --放路由代码的文件夹
			 >index.js
    		 >main.css
		 >>>public --放静态资源代码的文件夹
		   >>>css
			 >list.css
    		 >main.css
		 >>>views  --放模板文件代码的文件夹
			>index.art/html
    		>list.art/html
    	 >>>model --放和数据库相关的代码的文件夹
		   >connect.js
		   >user.js

//初始化,cmd
npm init -y
npm install art-template
npm install serve-static
npm install dateformat
nodemon app.js   //开启服务器

//app.js文件
const http = require('http');
const url = require('url');
const path = require('path');

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

//4.1实现静态资源访问模块,使页面可以加载link的css文件
const serve = serveStatic(path.join(_dirname) ,'public')

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

require('./model/connect'); //引入connect.js模块
//引入router
const router = require('./route/index');

//1.0创建服务器
const app = http.createServer();

//1.2为服务器对象添加请求事件
app.on('request', async (req, res) => {
	//res.end('ok');
	router(req,res, () => {}) //2.3调用路由,启用路由使生效
	serve(req,res, () => {})  //4.2 启用静态资源访问服务功能
};
//监听端口
app.listen(80);
console.log('服务器启动成功');

//index.js文件
const querystring = require('querystring'); //5.3 
//2.0引入router模块
const getRouter = require('router');
//2.0获取路由对象
const router = getRouter();
const Student = require('../model/user');  //引入user.js模块
//3.0引入模板引擎
const template = require('art-template');

//编写路由具体路径
//2.1呈递学生档案信息页面
router.get('/add',(req,res) => {
	//3.2配置返回的模板解析页面
	let html = template('index.art',{}); //暂时没有数据,第二个参数为空
	res.end(html);
})
//2.2呈递学生档案信息列表页面
router.get('/list', async(req,res) => {
	//查询学生信息
 	let students =	await Student.find(); //返回的students是一个数组
	let html = template('list.art',{
		students: students
	}); 
	res.end(html);
})
//5.1实现学生信息添加功能路由
router.post('/add',(req,res) => {
	//5.2接受post请求参数
	let formData = '';
	req.on('data', param => {
		formData += param;
	});
	req.on('end', async () => {
		console.log(formData)
		//querystring.parse(formData); //5.3将参数解析为对象
		//5.4获取的数据插入数据库
	    await Student.create(querystring.parse(formData)) 
		res.writeHead(301,{
			Location: '/list'
		});
		res.end();
	})
})
module.exports = router;


//connect.js文件
const mongoose = require('mongoose'); //第三方模块,安装

//2.0数据库连接, 需要启动数据库
mongoose.connect('mongodb://localhost:27017/playground',{useNewUrlParser: true})
		.then(() => console.log('数据库连接成功'))
		.catch(() => console.log('数据库连接失败'))

//user.js文件
//2.1创建用户集合规则 
const studentsSchema = new mongoose.Schema({
		name: {
			type: String,
			required: true,
			minlength: 2,
			maxlength: 20
		},
		age: {
			type: Number,
			min: 10,
			max: 25,
		},
		sex: String,
		email: String,
		hobbies: [ String ]  //是一个数组,数组中每一个应该为字符串
		collage: String,
		enterDate: {
			type: Date,
			default: Date.now
		}
})	

//2.2创建集合 返回集合构造函数
const Student = mongoose.model('Student',userSchema);

//2.3插入数据,在cmd操作
mongoimport -d playground -c users -file ./user.json

//导出	
module.exports = Student;

//index.art文件
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>学生档案</title>
    <link rel="stylesheet"  href="./css/main.css">
    
</head>

<body>
   <form action="/add" method='post'>
       <fieldset>
           <legend>学生档案</legend>
           <label>
            <!-- name属性的名字和数据库表单字段一样 -->
               姓名: <input class="normal" type="text" placeholder="请输入姓名" autofocus 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 type="checkbox" value="敲代码" name="hobbies">敲代码
               <input type="checkbox" value="打篮球" name="hobbies">打篮球
			  <input type="checkbox" value="睡觉" name="hobbies">睡觉	
           </label>
       </fieldset>
   </form>
</body>
</html>

//list.art文件
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>学员信息</title>
    <link rel="stylesheet"  href="./css/main.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.eamil}}</th>
           <!-- {{$value.hobbies}} -->
           <th>
               {{each $value.hobbies}}
                <span>{{$value}}</span>
               {{/each}}
           </th>
           <th>{{$value.collage}}</th>
           <!-- <th>{{$value.enterDate}}</th> -->
           <th>{{dateformat($value.enterDate),'yyyy-mm-dd'}}</th>
       </tr>
       {{/each}}
   </table>
</body>
</html>
  1. Express

5.1 Express

1. 什么是Express框架
-是一个基于Node平台的web应用开发框架,可以用来创建各种Web应用
-安装
npm install express

--- Express框架特性
-提供了方便简洁的路由定义方式
-对获取的HTTP请求参数进行了简化处理
-对模板引擎支持度度高,方便渲染动态HTML页面
-提供了中间件机制有效控制HTTP请求
-拥有大量的第三方中间件对功能及逆行扩展

---.实例
//引入express框架
const express = require('express');
//创建网站服务器
const app = express();

app.get('/', (req,res) => {
    //send()
    //1.send方法内部会检测响应内容的类型
    //2.send方法会自动设置http状态码
    //3.send方法会自动设置相应的内容类型及编码
    res.send('Hello. Express');
})

app.get('/list', (req,res) => {
    res.send({name: 'zhangsan',age: 20})
})
//监听端口
app.listen(3000);
console.log('网站服务器启动成功')


2. 什么是中间件
-中间件就是一堆方法,可以接收客户端发来的请求,可以对请求做出响应,也可以将请求继续交给下一个中间件继续处理
-可以针对同一个请求设置多个中间件,对同一个请求进行多次处理,默认情况下,请求是从上到下依次匹配中间件,一旦匹配成功终止匹配。但是可以调用next方法将请求的控制权交给下一个中间件,直到遇到结束请求的中间件

//实例
app.get('/request', (req,res,next) => {
    req.name = 'zhangsan';
    next();
});

app.get('/request',(req,res) => {
    res.send(req.name);
})


--app.use中间件
该方法匹配所有的请求方式,可以直接传入请求处理函数,代表接收所有的请求

//实例,接收所有请求的中间件
app.use((req,res,next) => {
    console.log(req.url);
    next();
});

-app.use第一个参数也可以传入请求地址,代表不论什么请求方式,只要是这个请求地址就可以接收这个请求

app.use('/admin', (req,res,next) => {
    console.log(req.url);
    next();
})


3.中间件的应用
-1.路由保护,客户端在访问需要登陆的页面时,可以先使用中间件判断用户登陆状态,用户如果未登录,则拦截请求,直接响应禁止用户进入需要登陆的界面
-2.网站维护公告,在所有的路由的最上面定义接收所有请求的中间件,直接为客户端做出响应,网站正在维护中
-3.自定义404页面

4.错误处理中间件
-错误处理中间件是一个集中处理错误的地方,比如文件读取失败,数据库连接失败
-当程序出现错误时,调用next()方法,并且将错误信息通过参数的形式传递给next()方法,即可触发错误处理中间件。即无法捕捉异步代码出错,需要添加next()方法手动进行捕捉

//实例1
app.use((err,req,res,next) => {
    res.status(500).send('服务器发生未知错误');
})

//实例,异步错误2
app.get('/', (req,res,next) => {
    fs.readFile('/file-does-not-exist', (err,data) => {
        if (err){
            next(err);
        }
    });
});

//实例,模拟同步方法错误3
const express = require('express');
const app = express();

app.get('/index', (req,res) => {
    throw new Error('程序发生了未知错误') //同步方法,模拟一个错误
})

//错误处理中间
app.use((err,req,res,next) => {
    //res.status(500).send('服务器端错误');
    res.status(500).send(err.message);
    //访问/index时,提示:程序发生了未知错误
})

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

//实例,模拟异步方法错误4
app.get('/index', (req,res,next) => {
    fs.readFile('./demo.txt','utf8',(err,result) => {
        if (err != null) {
            next(err);
        } else {
            res.send(result)
        }
    })
})

//错误处理中间
app.use((err,req,res,next) => {
    //res.status(500).send('服务器端错误');
    res.status(500).send(err.message);
    //访问/index时,提示:程序发生了未知错误
})

5. 捕获错误
- 在node.js中,异步API的错误信息都是通过回调函数获取的,支持Promise对象的异步API发生错误可以通过catch方法捕获
- try catch可以捕获异步函数以及其他同步代码在执行过程中发生的错误,但是不能捕获其他类型的API发生的错误

//实例1
app.get('/',async (req,res,next) => {
    try {
        await User.find({name: 'zhangsan'})
    } catch(ex) {
        next (ex);
    }
});

//实例2
app.get('index', async(req,res,next) => {
    try {
         //如果这段代码出现错误,会跳转到catch里面执行
        await readFile('./aa.js') 
    } catch (ex) {
        next(ex);
    }
})

5.2 实例代码

//路由保护案例
//检测是否登录
//引入express框架
const express = require('express');
//创建网站服务器
const app = express();

app.use('/admin',(req,res,next) => {
    //用户没有登陆
    let isLogin = false;
    //如果用户登录
    if(isLogin) {
        //让请求继续向下执行
        next()
    } else {
        res.send('您还没有登录,不能访问/admin这个页面')
    }
})
app.get('/admin',(req,res) => {
    res.end('您已登录,可以访问当前页面')
}) 
//监听端口
app.listen(3000);
console.log('网站服务器启动成功')

//网站维护公告,定义在所有请求之前,拦截所有请求
app.use((req,res,next) => {
    res.send('当前网站正在维护....')
})

//自定义404,放在所有请求之后
app.use((req,res,next) => {
    res.status(404).send('当前页面不存在')
})

5.3 Express请求处理

1.模块化路由简介

//实例
//引入express框架
const express = require('express')
//创建路由对象
const home = express.Router();
//将路由和请求路径进行匹配
app.use('/home',home);
//在home路由下继续创建路由
home.get('/index', ()=> {
    //  /home/index
    res.send('欢迎来到博客展示页面')
})

//实例
const express = require('express');
//创建网站服务器
const app = express();
//创建路由对象
const home = express.Router();
//将路由和请求路径进行匹配
app.use('/home',home);
//在home路由下继续创建二级路由
home.get('/index', (req,res)=> {
    //  /home/index
    res.send('欢迎来到博客展示页面')
})

2. 构建模块化路由

//实例1
//home.js文件
const express = require('express');
const home = express.Router();
//在home路由下再挂载一个路由
home.get('/index', ()=>{
    res.send('欢迎来到博客展示页面');
});
module.exports = home;

//admin.js文件
const express = require('express');
const express = require('express');
const admin = express.Router();
const admin = express.Router();
admin.get('/index', ()=>{
    res.send('欢迎来到博客管理页面');
});
module.exports = admin;

//app.js文件
const home = require('./route/home.js');
const admin = require('./route/admin.js');
//访问/home时,用home路由去处理
app.use('/home',home);  
app.use('/admin',admin);

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

//实例
//接收地址栏中间问号后面的参数
//http://localhost:3000/?name=zhangsan&age=30
app.get('/',(req,res) => {
    console.log(req.query); //{'name':'zhangsan', 'age': '30'}
});
 
4. POST参数的获取
-Express中接收post请求参数需要借助第三方包 body-parser
-安装
npm install body-parser

//实例
//引入body-parser模块
const bodyParser = require('body-parser')
//配置body-parser模块
//extended: false 方法内部使用querystring模块处理请求参数的格式
//extended: true 方法内部使用第三方模块qs处理请求参数的格式
app.use(bodyParser.urlencoded({ extended: false }));
//接收请求
app.post('/add', (req,res) => {
    //接收请求参数
    console.log(req.body);
})

//实例
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
app.use(bodyParser.urlencoded({ extended: false }));
app.post('/add', (req,res) => {
    res.send(req.body); //{'username':'zhangan','password':'123456'}
})
app.listen(3000);
console.log('网站服务器启动成功')

5.Express路由参数

//实例
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
app.use(bodyParser.urlencoded({ extended: false }));

app.get('/index/:id', (req,res) => {
    res.send(req.parsma)  //req.parsma获取传过来的id {'id':'123'}
})
app.get('/index/:id/:name/:age', (req,res) => {
    res.send(req.params)  // {'id':'123','name':'zhangsan','age':'20'}
})
app.listen(3000);
console.log('网站服务器启动成功')

6.静态资源的处理
-通过Express内置的express.static可以方便的托管静态文件,例如:img、CSS、JavaScript文件等

-访问public目录下文件
app.use(express.static('public'));

//实例
const express = require('express');
const path = require('path');
const app = express();

//实现静态资源访问功能,开放public文件夹
app.use(express.static(path.join(_dirname, 'public')))

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

7.模板引擎
-在基础上封装了express-art-template
-安装
npm install art-template 
npm express-art-template

//实例1
//当渲染后缀为art的模板时,使用 express-art-template
//1.告诉express框架使用什么模板引擎渲染什么后缀的模板文件,参数一:模板后缀,二使用的模板引擎
app.engine('art', require('express-art-template'));
//设置模板存放目录 views文件夹
//2.告诉express框架模板存放的位置是什么
app.set('views',path.join(_dirname,'views'));
//渲染模板时不写后缀,默认拼接art后缀
//3.告诉expre框架模板的默认后缀是什么
app.set('view engine','art');
app.get('/',(req,res) => {
    //1.拼接模板路径
    //2.拼接模板后缀
    //3.哪一个模板和哪一个数据进行拼接
    //4.将拼接结果相应给了客户端
    res.render('index',{});
})

//实例2
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.listen(3000);
app.get('/index',(req,res) => {
    //渲染模板,参数一:模板名字即index.art文件,二渲染的数据对象
    res.render('index',{
        msg: 'message'
    });
})

console.log('网站服务器启动成功')

8.app.locals 对象
-将变量设置到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.listen(3000);

//将变量设置到app.locals对象下面
app.locals.users = [{
    name: 'zhangsan',
    age: 20
},{
    name: 'lisi',
    age: 30
}]

app.get('/index',(req,res) => {
    //渲染模板,参数一:模板名字即index.art文件,二渲染的数据对象
    res.render('index',{
        msg: 'message'
    });
})
app.get('/list',(req,res) => {
    //渲染模板,参数一:模板名字即index.art文件,二渲染的数据对象
    res.render('list',{
        msg: 'message'
    });
})

console.log('网站服务器启动成功')

//index.art文件下 和 list.art下
<ul>
    {{ msg }}
     {{each users}}
    <li>
        {{$value.name}}
        {{$value.age}}
    </li>
     {{/each}}
</ul>

5.4 Express综合案例

5.4 博客项目

//博客项目
//项目准备
1. 建立项目所需的文件夹
2. 初始化项目描述文件cmd
3. 下载项目所需的第三方模块
4. 创建网站服务器
5. 构建模块化路由
6. 构建博客管理页面模板

//文件结构
>>>>blog文件夹
   >app.js
   >hash.js 
   >joi.js 

   >>>middleware 中间件文件夹
     >loginguard.js

   >>>public 静态资源 
   	 >>>home  home静态资源文件夹
	   >>>css
	   >>>images
	   >>>js
	   >>>lib
   	 >>>admin  admin静态资源文件夹
       >>>css
       >>>js
		 >common.js  抽取公共js代码
   	   >>>images
   	   >>>lib

   >>>model  数据库操作
   	 >connect.js 连接数据库
   	 >user.js 创建用户集合
     
   >>>route  路由
   	 >>>admin  分散路由文件
	   >login.js
	   >user.edit.js
     >home.js 博客展示页面路由
     >admin.js

   >>>views  模板
     >>>common 抽取公共代码
       >aside.art  侧边栏代码
       >header.art  头部header组件代码
       >layout.art  头部标识代码
       >
           
   	 >>>admin  admin模板资源文件夹
       >article.html/.art
   	   >article-edit.html/.art
   	   >login.html/.art
   	   >user.html/.art
   	   >user-edit.html/.art
   	   >error.html/.art
       
	 >>>home  home模板资源文件夹
       >article.html/.art
       >default.html/.art

//数据库结构
>>>blog
  >
  >
  >
       
       
//初始化以及第三方模块
npm init -y
npm install express mongoose art-template express-art-template
npm install body-parser

-登陆功能实现
1. 创建用户集合,初始化用户
	1. 连接数据库
    2. 创建用户集合
    3. 初始化用户
2. 为登陆表单项设置请求地址,请求方式以及表单项name属性
3. 当用户点击登陆按钮时,客户端验证用户是否填了登陆表单
4. 如果其中之一项没有输入,阻止表单提交
5. 服务器端接收请求参数,验证用户是否填写了登陆表单
6. 如果其中之一项没有输入,为客户端做出响应,阻止程序向下执行
7. 根据邮箱地址查询用户信息
8. 如果用户不存在,为客户端做出响应,阻止程序向下执行
9. 如果用户存在, 将用户名和密码进行对比
10. 对比成功,用户登陆成功
11. 对比失败,用户登陆失败

-新增用户
1. 为用户列表页面的新增用户按钮添加连接
2. 添加一个连接对应的路由,在路由处理函数中渲染新增用户模板
3. 为新增用户表单指定请求地址,请求方式,为表单项添加name属性
4. 增加实现添加用户的功能路由
5. 接收到客户端传递过来的请求参数
6. 对请求参数的格式进行验证
7. 验证当前要注册的邮箱地址是否已经注册过
8. 对密码进行加密处理
9. 将用户信息添加到数据库中
10. 重定向页面到用户列表页面

5.4.0 项目包含知识点

1. 密码加密 bcrypt
哈希加密是单程加密方式
在加密的密码中加入随机字符串可以增加密码被破解的难度

//导入bcrypt模块
const bcrypt = require('bcrypt');
//生成随机字符串 gen => generate 生成salt盐
let salt = await bcrypt.genSalt(10);
//使用随机字符串对密码进行加密
let pass = await bcrypt.hash('明文密码',salt);

-bcrypt依赖的其他环境
1.python 2.x
2.node-gyp 
npm install -g node-gyp 
3.windows-build-tools 
npm install --global --production windows-build-tools

-安装
npm install bcrypt

-密码比对
let isEqual = await bcrypt.compare('明文密码', '加密密码');

2.cookie 与 session
--cookie: 浏览器在电脑硬盘中开辟的一块空间,主要供服务器端存储数据
-cookie中的数据是以域名的形式进行区分的
-cookie中的数据是有过期时间的,超过时间数据会被浏览器自动删除
-cookie中的数据会随着请求被自动发送到服务器端
--session: 是一个对象,存储在服务器端的内存中,在session对象中也可以存储多条数据,每一条数据都有一个sessionid作为唯一标识

-安装
-在node.js中使用express-session实现session功能
npm install express-session

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

3.Joi
-javaScript对象的规则描述和验证器
-安装
npm i nstall joi

const Joi = require('joi');
 
const schema = Joi.object().keys({
    username: Joi.string().alphanum().min(3).max(30).required(),
    password: Joi.string().regex(/^[a-zA-Z0-9]{3,30}$/),
    access_token: [Joi.string(), Joi.number()],
    birthyear: Joi.number().integer().min(1900).max(2013),
    email: Joi.string().email({ minDomainAtoms: 2 })
}).with('username', 'birthyear').without('password', 'access_token');

const result = Joi.validate({ username: 'abc', birthyear: 1994 }, schema);

Joi.validate({ username: 'abc', birthyear: 1994 }, schema, function (err, value) { });  

5.4.1 app.js

// app.js文件
// app.js文件
//1-2-3-4-5-6-7
//1.0引入express框架
const express = require('express');
//3.0引入处理路径
const path = require('path');
//6.0 引入参数解析模块,用来处理post请求参数
// npm install body-parser
const bodyPaser = require('body-parser');
//7.0导入 express-session模块
const session = require('express-session');

//1.1创建网站服务器
const app = express();

//5.0引入数据库模块,数据库连接
require('./model/connetct');
//5.1测试引入,测试后注销
// require('./model/user');

//6.2处理post请求参数
app.use(bodyPaser.urlencoded({extended: false}));
//7.1
app.use(session({secret: 'secret key'}));

//4.1告诉express框架模板所在的位置
app.set('views',path.join(_dirname,'views'));
//4.2告诉express框架模板的默认后缀是什么
app.set('view engine','art');
//4.3所使用的模板引擎
app.engine('art', require('express-art-template'));

//3.1开放静态资源文件 localhost/home/default.html
app.use(express.static(path.join(_dirname,'public')))

//2.0引入路由模块
const home = require('./route/home');
const admin = require('./route/admin');

//7.3拦截请求,判断用户登陆状态
app.use('/admin', require('./middleware/loginGuard.js'))

//2.1为路由模块匹配请求路径
app.use('/home', home);
app.use('/admin', admin);

//1.3监听端口,上线后为80
app.listen(80);
console.log('网站服务器启动成功,请访问localhost');


//hash.js文件
//引入bcrypt
const bcrypt = require('bcrypt');

async function run() {

//生成随机字符串
//genSalt方法接受一个数值作为参数
//数值越大,生成的随机字符串复杂度越高
//数值越小,生成的随机字符串复杂度越低
const salt = await bcrypt.genSalt(10);
//对密码进行加密
//1. 要进行加密的明文
//2. 随机字符串
const result = await bcrypt.hash('123456', salt);
}

run();


//joi.js文件
//引入joi模块
const Joi = require('joi');
//定义对象的验证规则
const schema = {
    username.Joi.string().min().max(5).required().error(new Error('username属性没有验证通过')),
	birth: Joi.number().min(1900).max(2020).error(new Error('birth属性没有验证通过')),
};

async function run() {
	//异步验证,错误需要用try catch捕获
    try {
    //实施验证
	await Joi.validate({username: 'ab'}, schema);
    }catch (ex){
        console.log(ex.message);
        return;
    }
    console.log('验证通过')
}

run();

5.4.2 Router文件夹

//home.js文件
// home.js
//1.0引入express框架
const express = require('express');
//1.1创建博客展示页面路由
const home = express.Router();

home.get('/', (req,res) => {
	res.end('欢迎来到博客首页')
});

//将路由对象作为模块成员进行导出
module.esports = home;

//admin.js文件
// admin.js
//1-2-3-4
//1.0引入express框架
const express = require('express');
//3.0导入用户集合构造函数
const { User } = require('../model/user');
//4.0引入bcrypt加密模块
const bcrypt = require('bcrypt');

//1.1创建博客管理页面路由
const admin = express.Router();

// 2.1 /admin/login
admin.get('/login', (req,res) => {
	res.render('admin/login') //view目录下
});

//2.2 实现登陆功能
admin.post('/login', async (req,res) => {
	//接收请求参数,使用body-parser解析的结果
	// res.send(req.body);
	const {email,password} = req.body;
	//如果用户没有输入邮件地址
	// if (email.trim().length == 0 || password.trim().length == 0) {
	// 	return res.status(400).send('<h4>邮件地址或密码错误</h4>');
	// }

	if (email.trim().length == 0 || password.trim().length == 0) {
		return res.status(400).render('admin/error',{msg:'邮件地址或密码错误'});
	}

	// if (password.trim().length == 0) {
	// 	return res.status(400).send('<h4>邮件地址或密码错误</h4>');
	// }

	//3.1根据邮箱地址查询用户信息
	//如果查询到了用户,user变量的值是对象类型,对象中存储的是用户信息
	//如果没有查询到用户,user变量为空
 	let user = await User.findOne({email});
 	//查询到了用户
 	if (user) {
 		//客户端传递过来的密码和用户信息及进行比较
 		let isValid = await bcrypt.compare(password,user.password);
 		if ( isValid ){
 			//登陆成功
 			//将用户名储存在请求对象中,加入session验证
 			req.session.username = user.username
 			res.send('登陆成功')
 		} else {
 			//没有查询到用户
 			res.status(400).render('admin/error', {msg: '邮件地址或密码错误'})
 		}
 	} else {
 		//没有查询到用户
 		res.status(400).render('admin/error', {msg:'邮件地址或密码错误'})
 	}
});

//1.2创建用户列表路由 /admin/user
admin.get('/user', (req,res) => {
	res.render('admin/user', {
		msg: req.session.username
	}) //view目录下
});

//1.3将路由对象作为模块成员进行导出
module.esports = admin;


//admin > login.js

//3.0导入用户集合构造函数
const { User } = require('../../model/user');
//4.0引入bcrypt加密模块
const bcrypt = require('bcrypt');
const login = async (req,res) => {
	//接收请求参数,使用body-parser解析的结果
	// res.send(req.body);
	const {email,password} = req.body;
	//如果用户没有输入邮件地址
	// if (email.trim().length == 0 || password.trim().length == 0) {
	// 	return res.status(400).send('<h4>邮件地址或密码错误</h4>');
	// }

	if (email.trim().length == 0 || password.trim().length == 0) {
		return res.status(400).render('admin/error',{msg:'邮件地址或密码错误'});
	}

	// if (password.trim().length == 0) {
	// 	return res.status(400).send('<h4>邮件地址或密码错误</h4>');
	// }

	//3.1根据邮箱地址查询用户信息
	//如果查询到了用户,user变量的值是对象类型,对象中存储的是用户信息
	//如果没有查询到用户,user变量为空
 	let user = await User.findOne({email});
 	//查询到了用户
 	if (user) {
 		//客户端传递过来的密码和用户信息及进行比较
 		let isValid = await bcrypt.compare(password,user.password);
 		if ( isValid ){
 			//登陆成功
 			//将用户名储存在请求对象中,加入session验证
 			req.session.username = user.username
 			res.send('登陆成功')
 		} else {
 			//没有查询到用户
 			res.status(400).render('admin/error', {msg: '邮件地址或密码错误'})
 		}
 	} else {
 		//没有查询到用户
 		res.status(400).render('admin/error', {msg:'邮件地址或密码错误'})
 	}
});
module.esports = login;

//user.edit.js
module.exports = async (req,res) =>{
	const {message} = req.query;
	res.render('admin/user-edit',{
		message: message //把错误信息传递过去
	});
}

//user-edit-fn.js
const Joi = require Joi = ('joi');
//引入用户集合的构造函数
const { User } = require('../../model/user');

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

	//定义对象的验证规则
	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('状态值非法'))
	};

	try {
    //实施验证
  	await Joi.validate(req.body, schema);
	} cathch(e){
		//e.message
		//验证没有通过,重定向回用户添加页面,把错误信息传递给
		res.redirect(`/admin/user-edit?message=$(e.message)`)
	}

	//根据邮箱地址查询用户是否存在
	let user = User.findOne({email: req.body.email});

	if (user) {
		//重定向回用户添加页面,把错误信息传递给
		res.redirect(`/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');

5.4.3 home静态资源文件夹

5.4.4 admin静态资源文件夹

---admin静态资源文件夹
//common.js 文件
//公共方法,获取表单内容
 function serializeToJson(from) {
    var result = {};
    
    //返回值为 [{name: 'email', value: '用户输入的内容'}]
    var f = from.serializeArray();
    f.forEach(function (item){
        //result.email 
        result[item.name] = item.value;
    }); 
    return result;
 }

5.4.5 views 模板文件夹

//1.login.html/.art文件
//模板文件中的静态资源文件的相对路径是相对于地址栏中的请求路径
//模板文件中引入外链资源使用绝对路径 / 
<link rel="stylesheet" href="/admin/lib/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="/admin/css/base.css">
<script src='/admin/lib/jquery/dist/jquery.min.js'></script>
<script src='/admin/lib/bootstrap/js/bootstrap.min.js'></script>

//2.user.art文件中引入公共文件header.art,aside.art
//admin文件夹下的模板文件都可以引入
<body>
    //子模版的相对路径是相对当前文件,因为它是由模板引擎解析的而不是浏览器
    {{include './common/header.art'}}
    <div class='./common/header.art'>
     	{{include './common/aside.art'}} 
        <div></div>  
     </div>
	//下面拼接单独代码
</body>


//3.layout.art  头部标识文件代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Blog-Content Manager</title>
    <link rel="stylesheet"  href="./css/main.css">
    {{block 'link'}}{{/block}}
</head>
                      
<body>
 	{{block 'main'}}{{/block}} //留坑,让使用的文件去填
	<script src='/admin/lib/jquery/dist/jquery.min.js'></script>
	<script src='/admin/lib/bootstrap/js/bootstrap.min.js'></script> 
     <script src="/admin/js/common.js"></script>                 
    {{block 'script'}}{{/block}}  
</body>
</html>        
 
//4.user.art文件中引入公共文件header.art,aside.art
//继承骨架代码,填充骨架代码
    {{extend './common/layout.art'}} //继承骨架代码中的layout
    {{block 'main'}}  //填充骨架代码中的main模块              
    {{include './common/header.art'}}
    <div class='./common/header.art'>
     	{{include './common/aside.art'}} 
        <div></div>  
     </div>
	 {{/block}}

5.4.5.2 views admin文件夹

-admin
//1.login.art文件
<div class="login">
    <!-- 设置提交方式 -->
    <form action="admin/login" method="post" id="loginForm">
    <div class="form-group">
        <label>邮件</label>
        <input type="email" class="form-control" placeholder="请输入邮件地址" name="email">
    </div>
    <div class="form-group">
        <label>密码</label>
        <input type="password" class="form-control" placeholder="请输入密码" name="password">
    </div>
    <button type="submit" class="btn btn-primary">登陆</button>
</form>
</div>

<!-- 进行客户端验证 -->
<script src="/admin/js/common.js"></script>
<script type="text/javascript">
 //提取至公共代码   
 // function serializeToJson(from) {
 //    var result = {};
    
 //    //返回值为 [{name: 'email', value: '用户输入的内容'}]
 //    var f = from.serializeArray();
 //    f.forEach(function (item){
 //        //result.email 
 //        result[item.name] = item.value;
 //    }); 
 //    return result;
 // }

//为表单添加提交事件
 $('#loginForm').on('submit', functon(){
    
    //获取到表单中用户输入的内容
    var result = serializeToJson($(this));
    //如果用户没有输入邮件内容的话
    if (result.email.trim().length == 0){
        alert('请输入邮件地址');
        //阻止程序向下执行
        return false;
    }

    if (result.password.trim().length == 0) {
        alert('请输入密码');
        return false;
    }

    //阻止表单默认提交行为,分流条件返回阻止
    //return false;
 });
</script>

//2.error.html/.art文件
{{extend './common/layout.art'}}

{{block 'main'}}
   //<p class='bg-danger error'>错误提示</p>
   <p class='bg-danger error'>{{msg}}</p>
{{/block}}
//定时跳转
{{block 'script'}}
  <scrept>
  setTimeout(function() {
  	location.href = '/admin/login';
  }, 3000)
  </scrept>
{{/block}}  

//3. user.html/.art文件
<div>
 <h4> 用户 {{msg ? msg: '用户名不存在'}} </h4>    
<div>

5.4.6 model 数据库操作

//1. connect.js 连接数据库

//引入mongoose第三方模块
const mongoose = require('mongoose'); //第三方模块,安装
//数据库连接, 需要启动数据库
mongoose.connect('mongodb://localhost:27017/blog',{useNewUrlParser: true})
		.then(() => console.log('数据库连接成功'))
		.catch(() => console.log('数据库连接失败'))

//2. user.js 创建用户集合
//user.js
//2.0创建用户集合
const mongoose = require('mongoose'); //第三方模块,安装
//引入bcrypt
const bcrypt = require('bcrypt');

//创建用户集合规则 
const userSchema = new mongoose.Schema({
		username: {
			type: String,
			required: true,
			minlength: 2,
			maxlength: 20
		},
		email: {
			type: String,
			//保证邮箱地址在插入数据库时不重复
			unique: true,
			required: true
		},
		password: {
			type: String,
			requires: true
		},
		//admin 超级管理员
		//normal 普通用户
		role: {
			type: String,
			required: true
		},
		// 0 启用
		// 1 禁用
		state: {
			type: Number,
			default: 0
		}

})	

//2.2创建集合 返回集合构造函数
const User = mongoose.model('User',userSchema);

//2.3插入数据,在cmd操作
mongoimport -d playground -c users -file ./user.json
//加密创建账号
async function createUser (){
	const salt = await bcrypt.genSalt(10);
	const pass = await bcrypt.hash('123456', salt);
 	const user = await	User.create({
	username: 'itrheima',
	email: '1234@126.com',
	password: pass,
	role: 'admin',
	state: 0
}).then(() => {
	console.log('用户创建成功')
}).catch(() => {
	console.log('用户创建失败')
})
}

//2.5 初始化管理员用户,测试使用,测试后注销
// User.create({
// 	username: 'itrheima',
// 	email: '1234@126.com',
// 	password: '123546',
// 	role: 'admin',
// 	state: 0
// }).then(() => {
// 	console.log('用户创建成功')
// }).catch(() => {
// 	console.log('用户创建失败')
// })

//2.4将用户集合作为模块成员导出	
module.exports = {
	User
}

5.7 中间件文件夹

//loginguard.js文件
const guard = (req,res,next) => {
	//判断用户访问的是否是登陆页面
	//判断用户的登陆状态
	//如果用户是登陆的,将请求放行
	//如果用户不是登陆的,将请求重定向到登陆页面
	if (req.url != '/login' && req.session.username) {
		res.redirect('/admin/login');
	} else {
		//用户是登录状态,将请求放行
		next();
	}
}

module.exports = guard;
  1. Ajax

6.1 Ajax运行原理及实现

1. Ajax是什么
-它是浏览器提供的一套方法,可以实现页面无刷新更新数据,提高用户浏览网站应用的体验

2. Ajax运行环境
Ajax技术需要运行在网站环境中才能生效

3. 应用场景
-页面上拉加载更多数据
-列表数据无刷新分页
-表单项离开焦点数据验证
-搜索框提示文字下拉列表

4. Ajax的实现步骤
-1. 创建Ajax对象
var xhr = new XMLHttpRequest();
-2. 告诉Ajax请求地址以及请求方式
xhr.open('get', 'http://www.example.com');
-3. 发送请求
xhr.send();
-4 获取服务器端给与客户端的响应数据
xhr.onload = function() {
    console.log(xhr.responseText);
}

5. 服务器端响应的数据格式
-在真实的项目中,服务器端大多数情况下会以JSON对象作为响应数据格式,当客户端拿到响应数据时,要将JSON数据和HTML字符串进行拼接,然后将拼接的结果展示在页面中
-在http请求与响应的过程中,无论是请求参数还是响应内容。如果是对象类型,最终都会被转换为对象字符串进行传输。

//返回对象转换
xhr.onload = function(){
    var responseText = JSON.parse(xhr.responseText)
    console.log(responseText)  //{"name": "zs"} => name: "zs"
    var str ='<h2>' + responseText.name +'<h2>';
    document.body.innerHTML = str;  //zs
}


6. GET请求参数传递
-传统方式是表单提交
-Ajax中允许自行拼接参数

// 拼接请求参数,使用Ajax发送GET
<script type="text/javascript">
    // 获取按钮元素
    var btn = document.getElementById('btn');
    // 获取姓名文本框
    var username = document.getElementById('username');
    // 获取年龄文本框
    var age = document.getElementById('age');
    // 为按钮添加点击事件
    btn.onclick = function() {
        // 创建Ajax对象
        var xhr = new XMLHttpRequest();
        // 获取用户在文本框中输入的值
        var nameValue = username.value;
        var ageValue = age.value;
        //拼接请求参数, 将请求参数加入请求地址后面
        var params = 'username=' + nameValue + '&age=' + ageValue;
        // 配置ajax对象
        xhr.open('get', 'http://localhost:3000/get?'+params);
        // 发送请求
        xhr.send();
        //获取服务器端给与客户端的响应数据
        xhr.onload = function() {
            console.log(xhr.responseText);
        }
    }    
</script>

//服务器端
app.get('/get', (req,res) => {
    res.send(req.query);
})

7. POST请求参数传递
-1. 设置请求参数格式的类型,post请求必须设置
xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded');	
xhr.send(params);

-2 设置请求参数格式的类型,post请求必须设置
xhr.setRequestHeader('Content-Type','application/json');	
xhr.send(JSON.stringify({name:'lisi', age:50}))
//将 json 对象转换为 json 字符串, 发送时转换,传送时为字符串格式
JSON.stringify()
//将 json 字符串转换为 json 对象,接受时转换
JSON.parse()


// 拼接请求参数,使用Ajax发送POST

 //拼接请求参数, 将请求参数加入请求地址后面
  var params = 'username=' + nameValue + '&age=' + ageValue;
 // 配置ajax对象
  xhr.open('post', 'http://localhost:3000/post');
 //设置请求参数格式的类型,(post请求必须设置)	  
	xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded');	
  // 发送请求,发送params参数
   xhr.send(params);
  //获取服务器端给与客户端的响应数据
   xhr.onload = function() {
    console.log(xhr.responseText);
  }

//服务器端
app.use(bodyParser.urlencoded()); 
app.use(bodyParser.json()); //对应json格式

app.get('/post', (req,res) => {
    res.send(req.body);
})

8. 获取服务器端的响应
--Ajax状态码
-在创建ajax对象,配置ajax对象,发送请求以及接收完服务器端响应数据,这个过程中的每一个步骤都会对应一个数值,这个数值就是Ajax状态码

-0: 请求未初始化(还没有调用open())
-1: 请求已建立,但是还没有发送(还没有调用send())
-2: 请求已经发送
-3: 请求正在处理中,通常响应中已经有部分数据可以用了
-4: 响应已经完成,可以获取并使用服务器的响应了

xhr.readyState //获取AJax状态码
onreadystatechange //当 Ajax 状态码发生变化时将自动触发该事件

//实例
 <script type="text/javascript">    
    // 1. 创建Ajax对象
    var xhr = new XMLHttpRequest();
	// 0 已经创建ajax对象,但是还没有对ajax对象进行配置
	console.log(xhr.readyDtate);
    // 2. 告诉Ajax请求地址以及请求方式
    xhr.open('get', 'http://localhost:3000/readystate');
	// 1 已经对ajax对象进行配置,但是还没有发送请求
	console.log(xhr.readyDtate);
   	
	//当ajax状态码发生变化时触发
	xhr.onreadystatechange = function() {
		//2 请求已经发送了        
		//3 已经接收到服务器端的部分数据了
        //4 服务器端的响应数据已经接收完成了
        console.log(xhr.readyDtate);
        //对ajax状态码进行判断
        //如果状态码的值为4就代表数据已经接收完成了
        if (xhr.readyState == 4){
            console.log(xhr.responseText);
        }
    }

	// 3. 发送请求
    xhr.send();
    </script>

//服务器端
app.get('/readystate', (req,res) => {
    res.send("hello");
})

9. 获取服务器端的响应对比
---两种获取服务器端相应方式的区别
-onload事件:不兼容IE低版本,不需要判断Ajax状态码,被调用次数为一次
-onreadystatechange事件:兼容IE低版本,需要判断Ajax状态码,被调用次数为多次
-推荐使用onload()方法

10. Ajax错误处理
-1. 网络畅通,服务器端能接收到请求,服务器端返回的结果不是预期结果。可以判断服务器端返回的状态码,分别进行处理。xhr.status获取http状态码
-2. 网络畅通,服务器端没有接收到请求,返回404状态码
-3. 网络畅通,服务器端能接收到请求,服务器端返回500状态码
-4. 网络中断,请求无法发送到服务器端。会触发xhr对象下面的onerror事件,在onerror事件处理函数中对错误进行处理

//案例
//服务器端
app.get('/error', (req,res) => {
    res.status(400).send('not ok');
})
//客户端
btn.onclick = function() {
        // 创建Ajax对象
        var xhr = new XMLHttpRequest();
        
        xhr.open('get', 'http://localhost:3000/error);
        // 发送请求
        xhr.send();
        //获取服务器端给与客户端的响应数据
        xhr.onload = function() {
            console.log(xhr.responseText);
            if (xhr.status == 400){
                alert('请求出错')
            }
        }
    	// 当网络中断时会触发onerror事件
    	xhr.onerror = function() {
            alert('网络中断,无法发送Ajax请求')
        }
}
    
11. 低版本IE浏览器缓存问题
- 在请求地址后面加请求参数,保证每一次请求中的请求参数的值不相同
xhr.open('get', 'http://www.baidu.com?t=' + Math.random());

6.2 代码示例

//Ajax运行环境,单个服务器
const express = require('express');
const path = require('path');
const app = express();

app.use(express.static(path.join(__dirname,'public')));

app.get('/first',(req,res) => {
    res.send('您已登录,可以访问当前页面')
})

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

//创建Ajax实例,拿到数据
-请求服务器端的first路由,返回该路由的返回数据
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>移动端适配布局</title>
</head>

<body>
    <script type="text/javascript">    

    // 1. 创建Ajax对象
    var xhr = new XMLHttpRequest();

   // <!-- -2. 告诉Ajax请求地址以及请求方式 -->
    xhr.open('get', 'http://localhost:3000/first');

   // <!-- -3. 发送请求 -->
    xhr.send();

    //<!-- -4 获取服务器端给与客户端的响应数据 -->
    xhr.onload = function() {
    console.log(xhr.responseText);
    }
    </script>
  
</body>
</html>


//实例,验证邮箱地址唯一性
-1. 获取文本框并为其添加离开焦点事件
-2. 离开焦点时,检测用户输入的邮箱地址是否符合规则
-3. 如果不符合规则,阻止程序向下执行并给出提示信息
-4. 向服务器端发送请求,检测邮箱地址是否被别人注册
-5. 根据服务器端返回值决定客户端显示何种提示信息

<head>
     <meta charset="UTF-8">
     <title>验证邮箱地址是否已经注册</title>
     <style type="text/css">
         p:not(:empty){
            padding: 15px;
         }
         .container{
            padding-top: 100px;
         }
     </style>
</head>
<body>
    <div class="container">
        <div class="form-group">
            <label>邮箱地址</label>
            <input type="email" class="form-control" id="email" name="">
        </div>
        <p id="info"></p>
    </div>
    <script type="text/javascript">
        var emailInp = document.getElementById('email');
        var info = document.getElementById('info');

        //当文本框离开焦点
        emailInp.onblur = function(){
            //获取用户输入的邮箱地址
            var email = this.value;
            var reg = /([0-9a-zA-Z_.-]+)[@]([0-9a-zA-Z_-]+)(([.][a-zA-Z]+){1,2})/;
            //如果用户输入的邮箱地址不符合规则
            if(!reg.test(email)){
                //给出用户提示
                info.innerHTML= '请输入符合规则的邮箱地址';
                info.className = 'bg-danger';
                return; 
            }
            //向服务端发送请求
            ajax({
                type: 'get',
                url: 'http:localhost:3000',
                data: {
                    email: eamil
                },
                success: function (result) {
                    // console.log(result);
                    info.innerHTML = result.message;
                    info.className = 'bg-success';
                },
                error: function (result) {
                    //console.log(result);
                    info.innerHTML = result.message;
                    info.className = 'bg-danger';
                }
            });
        }
    </script>
</body> 

//实例,搜索内容自动提示
-1. 获取搜索框并为其添加用户输入事件
-2. 获取用户输入的关键字
-3. 向服务器端发送请求并携带关键字作为请求参数
-4. 将响应数据显示在搜索框底部

<head>
     <meta charset="UTF-8">
     <title>搜索框输入文字自动提示</title>
     <style type="text/css">
        .container {
            padding-top: 150px;
        }
        .list-group{
            display: none;
        }
     </style>
</head>
<body>
    <div class="container">
        <div class="form-group">
            <input type="text" id="search" class="form-control">
        </div>
        <ul class="list-group" id="list-box">
            <!-- <li class="list-group-item">提示文字显示的地方</li> -->
        </ul>
        
    </div>
    <script  src="/js/ajax.js"></script>
    <script src="./js/template-web.js" ></script> 
    <script type="text/html" id="tpl">
        {{each result}}
			<li class="list-group-item">{{$value}}</li>
        {{/each}}
    </script>
    <script type="text/javascript">
       //获取搜索框
       var searchInp = document.getElementById('search');
       // 获取提示文字的存放容器
       var listBox = document.getElementById('list-box');
       //储存定时器的变量
       var timer = null;
       // 当用户在文本框中输入的时候触发
       searchInp.oninput = function() {
            //清除上一次开启的定时器
            clearTimeout(timer);
            //获取用户输入的内容
            var key = this.value;
            //如果用户没有在搜索框中输入内容
            if(key.trim().length == 0){
                //将提示框隐藏
                listBox.style.display = 'none';
                //阻止程序向下执行
                return;
            }
           //开启定时器,让请求延时发送
            timer  = setTimeout(function () {
            //向服务器端发送请求,索取和用户输入关键字相关的内容
            ajax({
                type: 'get',
                url: 'localhost:3000',
                data: {
                    key: key
                },
                success: function (result){ 
                    //使用模板引擎拼接字符串
                   var html = template('tpl',{result: result})
                   //将拼接好的字符串显示在页面中
                   listBox.innerHTML = html;
                   listBox.style.display == 'block';
                }
              })
           }, 800)
            
       } 
    </script>
</body>  

//实例,省市区三级联动
-1. 通过接口获取省份信息
-2. 使用Javascript和获取到省市区下拉框元素
-3. 将服务器端返回的省份信息显示在下拉框中
-4. 为下拉框元素添加表单值改变事件(onchange)
-5. 当用户选择省份时,根据省份id获取城市信息
-6. 当用户选择城市时,根据城市id获取县城信息

 <body>
    <div class="container">
        <div class="form-inline">
            <div class="form-group">
                <select class="form-control" id="province">
                    <option>请选择省份</option>
                </select>
            </div>
            <div class="form-group">
                <select class="form-control" id="city">
                    <option>请选择城市</option>
                </select>
            </div>
            <div class="form-group">
                <select class="form-control" id="area">
                    <option>请选择区县</option>
                </select>
            </div>
        </div>
    </div>


    <script  src="/js/ajax.js"></script>
    <script src="./js/template-web.js" ></script> 
    <!-- 省份模板 -->
    <script type="text/html" id="provinceTpl">
        {{each province}}
             <option>请选择省份</option>
             <option value="{{$value.id}}">${{value.name}}</option>
        {{/each}}
    </script>
     <!-- 城市模板 -->
    <script type="text/html" id="cityTpl">
        {{each city}}
             <option >请选择城市</option>
             <option value="{{$value.id}}">${{value.name}}</option>
        {{/each}}
    </script>
    <!-- 县区模板 -->
    <script type="text/html" id="areaTpl">
        {{each area}}
             <option value="{{$value.id}}">请选择县区</option>
             <option value="{{$value.id}}">${{value.name}}</option>
        {{/each}}
    </script>
    <script type="text/javascript">
        //获取下拉框元素
        var province = document.getElementById("province");
        var city = document.getElementById("city");
        var area = document.getElementById("area");

        //获取省份信息
        ajax({
            type: 'get',
            url: '',
            success: function (data) {
            // console.log(data) Array[{id: "001", name: '浙江省'},{id: "001", name: '浙江省'}]
            var html = template('provinceTpl',{province: data})
            };
            province.innerHTML = html;
        });

        //为省份的下拉框添加值改变事件
        province.onchange = function() {
            //获取省份id
            var pid = this.value

            //清空县城下拉框中的信息
            var html = template('areaTpl' ,{area: []});
            area.innerHTML = html;

            //根据id获取城市信息
            ajax({
                type: 'get',
                url: '/cities',
                data: {
                    id: pid
                },
                success: function(data){
                    var html = template('cityTpl',{city: data});
                    city.innerHTML = html;
                }
            })
        };

        //为城市的下拉框添加值改变事件
        province.onchange = function() {
            //获取城市id
            var cid = this.value
            //根据id获取县区信息
            ajax({
                type: 'get',
                url: '/areas',
                data: {
                    id: pid
                },
                success: function(data){
                    var html = template('areasTpl',{areas: data});
                    areas.innerHTML = html;
                }
            })
        }

    </script>

6.3 Ajax异步封装

1. Ajax 封装
- 将请求代码封装到函数中,发送请求时调用函数即可

//封装代码
<script type="text/javascript">

   function ajax(options) {
    // 创建Ajax对象
    var xhr = new XMLHttpRequest();
    // 配置ajax对象
    xhr.open(options.type, options.url);
    // 发送请求
    xhr.send();
    // 监听xhr对象下面的onload事件
    // 当xhr对象接收完响应数据后触发
    xhr.onload = function() {
        console.log(xhr.responseText);
        options.success(xhr.responseText);
     }
   }

   ajax({
        //请求方式
        type: 'get',
        // 请求地址
        url: 'http://localhost:3000/first',
        success: function (data){  //对返回值进行操作
            console.log('这里是success函数' + data)
        }
   })
</script>


//封装代码,完整版
<script type="text/javascript">

   function ajax(options) {

    //储存的是默认值
    var defaults = {
        type: 'get',
        url: '',
        data: '',
        header: {
             'Content-Type': 'application/x-www-form-urlencoded'
         },
         success: function(){},
         error: function(){},         
    }; 

    //使用options对象中的属性覆盖defaults对象的属性
    //如果ajax对象传入了相应参数就会覆盖默认参数,否则就使用默认值
    Object.assign(defaults,options);

    // 创建Ajax对象
    var xhr = new XMLHttpRequest();
    // 拼接请求参数的变量
    var params = '';
    for (var attr in defaults.data) {
        //拼接为 属性=属性值,将参数转换为字符串格式 
        params += attr + '=' + defaults.data[attr] + '&'; 
    }
    // 将参数最后面的&截取掉
     params = params.substr(0, params.length-1);

    // 配置ajax对象
    xhr.open(defaults.type, defaults.url);

     //判断请求方式
     if (defaults.type == 'post') {
        //用户希望的向服务器端传递的请求参数的类型
        var contentType = defaults.header['Content-Type']
        //设置请求参数格式类型
        xhr.setRequestHeader('Content-Type',contentType);  
        // 判断用户希望的请求参数格式的类型
        if (contentType == 'application/json') {
            //传递json类型的参数
            xhr.send(JSON.stringify(defaults.data) )
        } else {
            xhr.send(params);
        }
        
     } else {
        // 发送请求
          xhr.send();
     }
    // 监听xhr对象下面的onload事件,当xhr对象接收完响应数据后触发
    xhr.onload = function() {

        //获取响应头中的数据
        var contentType = xhr.getResponseHeader('Content-Type');
        //服务器端返回的数据
        var responseText = xhr.responseText
        if (contentType.includes('application/json')){
            responseText = JSON.parse(responseText)
        }   

        //console.log(xhr.responseText);
        if (xhr.status == 200){
            //请求成功,调用处理成功情况的函数
             defaults.success(responseText,xhr);
        } else {
            //请求失败,调用处理失败情况的函数
            defaults.error(responseText,xhr);
        }
      }
   }

   // ajax({
   //      //请求方式
   //      type: 'get',
   //      // 请求地址
   //      url: 'http://localhost:3000/first',
   //      data: {
   //          name: 'zhangsan',
   //          age: 20
   //      },
   //      header:{
   //          'Content-Type': 'application/json'
   //      },
   //      success: function (data){  //对返回值进行操作
   //          console.log('这里是success函数');
   //          console.log(data);

   //      },
   //      error: function (){ 
   //          console.log('这里是error函数' + data);
   //          console.log(xhr);
   //      }
   // })

   ajax({
     // 请求地址
        url: 'http://localhost:3000/first',
         success: function (data){  //对返回值进行操作
                 console.log('这里是success函数');
                 console.log(data);
   })

6.4 模板引擎

1. 使用步骤
-1. 下载 art-template 模板引擎库文件并在HTML页面中引入库文件
<script src="./js/template-web.js" ></script>

-2. 准备 art-template 模板
<script id="tpl" type="text/html">
	<div class="box"></div>
<script>
        
-3. 告诉模板引擎将哪一个模板和数据进行拼接
<script type="text/javascript">
var html = template('tpl', {username: 'zhangsan', age: '20'});
</script>

-4. 将拼接好的hmtl字符串添加到页面中
document.getElementById('container').innerHTML = html;

-5. 通过模板引擎语法告诉模板引擎,数据和html字符串要如何拼接
<script>
    <div class="box" > {{username}} </div>
<script>

6.5 FormData

1. FormData对象的使用
--作用
-1. 模拟HTML表单,相当于将HTML表单映射成表单对象,自动将表单对象中的数据拼接成请求参数的格式
-2. 异步上传二进制文件

//实例
-1, 准备 HTML 表单
<form id='form'>
	<input type="text" name="username" />
	<input type="password" name="password" />
	<input type="button"/>  
</form>

-2. 将 HTML 表单转化为 formData 对象
var form = document.getElementById('form');
//将普通的html表单转化为表单对象
var formData = new FormData(form);

-3. 提交表单对象
xhr.send(formData);

<script type="text/javascript">
    var btn = document.getElementById('btn');
    var form = document.getElementById('form');
    btn.onclick = function(){
        var formData = new FormData(form);
        var xhr = new XMLHttpRequest();
        xhr.open('post','http://localhost');
        xhr.send(formData);
        xhr.onload = function(){
            if(xhr.status == 200){
                console.log(xhr.responseText);
            }
        }
    }
</script>

//服务器端
const formidable = require('formidable');

app.post('/formData', (req,res) => {
    //创建formidable表单解析对象
    const form = new formidable.IncomingForm();
    // 解析客户端传递过来的FormData对象
    form.parse(req, (err,fields,files) => {
        res.send(fields);
    })
})

2. FormData 对象的实例方法
-1. 获取表单对象中属性的值
formData.get('key');
-2. 设置表单对象中属性的值
formData.set('key','value');
-3. 删除表单对象中属性的值
formData.delete('key');
-4. 向表单对象中追加属性值
formData.append('key','value');
-set和appnd方法的区别,在属性名已经存在的情况下,set会覆盖已有键名的值,append会保留两个值

3.  FormData二进制文件上传
<input type='file' id='file'/>

var file = document.getElementById('file');
//当用户选择文件的时候,在用户选择文件时触发
file.onchange = function (){
    //创建空表单对象
    var formData = new FormData();
    //将用户选择的二进制文件追加到表单对象中,this.files为用户选择的结果
    formData.append('attrName',this.files[0]);
    var xhr = new XMLHttpRequest();
    //配置ajax对象,请求方式必须为post
    xhr.open('open','www.baidu.com');
    xhr.send(formData);
    //监听服务器端响应给客户端的数据
    xhr.onload = function(){
        //请求成功
        if(xhr.status == 200) {
            console.log(xhr.responseText);
        }
    }
}

//服务器端
//实现文件上传路由
app.post('/upload',(req,res) =>{
    //创建formidable表单解析格式
    const form = new formidable.IncomingForm();
    //设置客户端上传文件的储存路径 /public/uploads
    form.uploadDir = path.join(__dirname,'public','uploads')
    //保留上传文件的后缀名字
    form.keepExtensions = true;
    //解析客户端传递过来的FormData对象
    form.parse(req, (err,fields,files) =>{
        res.send('ok');
    })    
})

4. FormData 文件上传进度展示

//代码示例
file.onchange = function () {
    xhr.upload.onprogress = function (ev){
        bar.style.width = (ev.loaded / ev.total) *100 + '%';
    }
}


//实例
<div class="container">
    <div class="form-group">
        <label>请选择文件</label>
        <input type="file" id="file">
        <br/>
        <div class="progress">
            <div class="progress-bar" style="width: 0%;" id="bar">0%</div> 
        </div>
    </div>
</div>

<script type="text/javascript">
    //当用户选择文件的时候,在用户选择文件时触发
file.onchange = function (){
    var file = document.getElementById('file');
    var bar = document.getElementById('bar');


    //创建空表单对象
    var formData = new FormData();
    //将用户选择的二进制文件追加到表单对象中,this.files为用户选择的结果
    formData.append('attrName',this.files[0]);
    var xhr = new XMLHttpRequest();
    //配置ajax对象,请求方式必须为post
    xhr.open('open','www.baidu.com');
    //在文件上传过程中持续触发
    xhr.upload.onprogress = function(ev) {
        // ev.loaded 文件已经上传了多少
        // ev.total 上传文件的总大小
      var result = (ev.loaded / ev.total) * 100 + '%';
      // 设置进度条的宽度
      bar.style.width = result;
      // 将百分比显示在进度条中
      bar.innerHTML = result;
    }

    xhr.send(formData);
    //监听服务器端响应给客户端的数据
    xhr.onload = function(){
        //请求成功
        if(xhr.status == 200) {
            console.log(xhr.responseText);
        }
    }
}
</script>

5. FormData 文件上传图片即预览
- 将图片上传到服务器端以后,服务器端通常会将图片地址作为响应数据传递给客户端,客户端可以从响应数据中获取图片地址,然后将土拍你再显示在页面中。 


//服务器端
//实现文件上传路由
app.post('/upload',(req,res) =>{
    //创建formidable表单解析格式
    const form = new formidable.IncomingForm();
    //设置客户端上传文件的储存路径 /public/uploads
    form.uploadDir = path.join(__dirname,'public','uploads')
    //保留上传文件的后缀名字
    form.keepExtensions = true;
    //解析客户端传递过来的FormData对象
    form.parse(req, (err,fields,files) =>{
        //将客户端传递过来的文件地址相应到客户端
        res.send({
            //以public为界限分割,得到两个数组,public前,public前,
            path: files.attrName.path.split('public')[1]
        });
    })    
})

6.6 Ajax同源与JSONP

1. Ajax请求限制
Ajax只能向自己的服务器发送请求

2. 同源
-两个页面拥有相同的协议,域名、端口。就说这两个页面属于同一个源

3. 使用JSONP解决同源限制问题
-jsonp是 json with padding的缩写,可以模拟AJax请求

-1. 将不同源的服务器端请求地址写在 script 标签的src属性中

    <script src="www.example.com"></script> 

-2. 服务器端响应数据必须是一个函数的调用,真正要发给客户端的数据需要作为函数调用的参数。
	const data = 'fn({name: 'zhangsna',age: '20'})';
	res.send(data)

-3. 在客户端全局作用域下定义函数fn
	function fn (data) { }

-4. 在 fn 内部对服务器端返回的数据进行处理
	function fn (data) { console.log(data); }

//实例
<body>
	<script >
		function fn(data) { 
			console.log('客户端的fn函数被调用了')
			console.log(data)
		}
	</script>
	<script src="http://localhost:3001/test"></script>
	<!-- 将非同源服务器端的请求地址写在script标签的src属性中 -->
</body>


//服务器端
app.get('/test', (req,res) =>{
	const result = 'fn({name:'zs'})';
	res.send(result)
})


4. JSONP代码优化
<script type="text/javascript">
	var btn1 = document.getElementById('btn1');
	var btn2 = document.getElementById('btn2');

	// 为按钮添加点击事件
	btn1.onclick = function(){
		jsonp({
			url: 'http://localhost:3001/better',
			data: {
				name: 'lisi',
				age: 30
			},
			success: function (data){
				console.log(123)
				console.log(data)
			}
		})
	}

	function jsonp (options){
		// 动态创建script标签
		var script = document.createElement('script');
		// 拼接字符串的变量
		var params = '';
		for (var attr in options.data){
			params += '&' + attr + '=' + options.data[attr];
		}

		// myJsonp0124741
		var fnName = 'myJsonp' + Math.random().toString().replace('.',' ');

		//使用window将它变成一个全局函数
		window[fnName] = options.success;
		// 为script标签添加src属性
		script.src = options.url + '?callback=' + fnName + params;
		// 将script标签追加到页面中
		document.body.appendChild(script);
		// 为script标签添加onload事件
		script.onload = function() {
			document.body.removeChild(script); 
		}

	}

//服务器端
app.get('better', (req,res) => {
    res.jsonp({name: 'lisi', age: 20});;
});

6.7 同源政策

1. CORS
-1. CORS全称是Cross-origin resource sharing,即跨域资源共享。它允许浏览器向跨服务器发送Ajax请求,克服Ajax只能同源使用的限制。
-2. 主要是在服务器端做配置,客户端仍发送Ajax请求

//服务器端
app.use((req,res, next) => {
    //1. 允许那些客户端访问我
    //* 代表允许所有的客户端访问我
    res.header('Access-Control-Allow-Origin','*')
    // 2. 允许客户端使用那些请求方式访问我
    res.header('Access-Control-Allow-Methods','get,post')
    next();
})

2. 同源政策
-同源政策是浏览器给与Ajax技术的限制,服务器端是不存在同源政策限制的

A浏览器端     请求>    A服务器端     请求>      B服务器端
            >>>>>>              >>>>>>
A浏览器端     <响应    A服务器端      <响应     B服务器端

//服务器端
//向其他服务器端请求数据的模块
const request = require('request');

app.get('/server',(req, res) => {
    request('http://localhost:3001/cross',(err,response,body) => {
        res.send(body);
    })
})

3. withCredentials 属性
-在使用Ajx技术发送跨域请求时,默认情况下不会在请求中携带cookie信息
-withCredentials: 指定在涉及到跨域请求时,是否携带cookie信息,默认为false
-Access-Control-Allow-Credentials: true 允许客户端发送请求时携带cookie

//客户端
xhr.withCredentials = true;
//服务器端
 res.header('Access-Control-Allow-Credentials',true);

6.8 jquery中的$.ajax()

1. Ajax与jquery
- $.ajax()方法  发送Ajax请求

//实例代码
<script type="text/javascript">
 $('#btn').on('click', function() {
 	$.ajax({
 		//请求方式
 		type: 'get',
 		//请求地址
 		url: 'http://localhost:3000/base',
 		// 请求成功后函数被调用
 		success: function(response) {
 			// response为服务器端返回的数据
 			// 自动将json字符串转换为json对象
 			console.log(response);
 		},
 		 请求失败后函数被调用
 		error: function (xhr){
 			consoel.log(xhr)
 		}
 	})
 })
</script>

2. 携带请求参数
//实例代码
<script type="text/javascript">
 $('#btn').on('click', function() {
 	$.ajax({
 		//请求方式
 		type: 'get',
 		//请求地址
 		url: 'http://localhost:3000/base',
        // 向服务器端发送的请求参数,传递对象会被自动转化为
        // name=zhangsan&age=100
        data: {
          name: 'zhangsan',
          age: 100
        },        
 		// 请求成功后函数被调用
 		success: function(response) {
 			// response为服务器端返回的数据
 			// 自动将json字符串转换为json对象
 			console.log(response);
 		},
 		 请求失败后函数被调用
 		error: function (xhr){
 			consoel.log(xhr)
 		}
 	})
 })
</script>

// 发送json格式请求参数

<script type="text/javascript">
 var params = {name: 'zs', age: 300}
 $('#btn').on('click', function() {
 	$.ajax({
 		//请求方式
 		type: 'get',
 		//请求地址
 		url: 'http://localhost:3000/base',
        // 向服务器端发送的请求参数
        data: JSON.sparams,
        //指定参数的格式类型
        contentType: 'application/json'
 		// 请求成功后函数被调用
 		success: function(response) {
 			// response为服务器端返回的数据
 			// 自动将json字符串转换为json对象
 			console.log(response);
 		},
 		// 请求失败后函数被调用
 		error: function (xhr){
 			consoel.log(xhr)
 		}
 	})
 })
</script>

// beforeSend 方法
<script type="text/javascript">

 $('#btn').on('click', function() {
 	$.ajax({
 		type: 'get',
 		url: 'http://localhost:3000/base',
        // 在请求发送之前调用
        beforeSend: function() {
          	alert('请求即将发送')  
            //请求不会被发送
            return false;
        },
 		success: function(response) {
 			console.log(response);
 		},
 		error: function (xhr){
 			consoel.log(xhr)
 		}
 	})
 })


3. serialize方法
-作用:将表单中的数据自动拼接为字符串类型的数据

//实例
//name=zhangsan&age=30 
var params = $('#form').serialize();

// 将表单中用户输入的内容转换为对象类型
function serializeObject (obj){
    //处理结果对象
    var result = {};
    // {name=zhangsan&age=30 }
    var params = obj.serializeArray();
    // 循环数组,将数组转换为对象类型
    $.each(params, function (index,value){
        result[value.name] = value.value;
    })
    // 将处理的结果返回到函数外部
    return result;
}

4. 发送JSONP请求
- $.ajax也可以发送JSPON请求

$.ajax({
    url: '',
    // 指定当前发送jsonp请求
	dataType: 'jspon',
    // 修改callback参数名称
    jsonp: 'cb',
    // 指定函数名称,一般不用
    jsonCallback: 'fnName',
    success: function (response) { }
})


5. $.get() $.post()

//使用实例
$.get('http://www.example.com', {name: 'zhangsan',age: 30}, function(response) {})
$.post('http://www.example.com', {name: 'zhangsan',age: 30}, function(response) {})

6.9 实例

6.10 jquery中ajax全局事件

1. 全局事件
-只要页面中有Ajax请求被发送,对应的全局事件就会被触发

.ajaxStart()  //当请求开始发送时触发
.ajaxComplete()  //当请求完成时触发

-NProgress
纳米级进度条动画组件

2. RESTful 风格的 API
GET      获取数据  http://www.example.com/users  						              http://www.example.com/users/1

POST	 添加数据  http://www.example.com/users
PUT		 更新数据  http://www.example.com/users/1
DELETE   删除数据  http://www.example.com/users/1

-实现
// 获取用户列表信息
app.get('/users',(req,res) => {
    res.end('当前是获取用户列表信息的路由')
});

// 获取某一个用户具体的信息的路由
app.get('/users/:id',(req,res) => {
    // 获取客户端传递过来的用户id
    const id = req.params.id;
    res.send(`当前是获取用户id为${id}的用户信息`)
});

//删除一个用户
app.delete('/users/:id',(req,res) => {
    // 获取客户端传递过来的用户id
    const id = req.params.id;
    res.send(`当前是删除用户id为${id}的用户信息`)
});

// 修改某一个用户具体的信息的路由
app.put('/users/:id',(req,res) => {
    // 获取客户端传递过来的用户id
    const id = req.params.id;
    res.send(`当前是在修改用户id为${id}的用户信息`)
});

6.11 XML基础

1. XML是什么
XML全称是 extensible markup language, 代表可扩展标记语言,它的作用是传输和储存数据

2. XML DOM
XML DOM 即XML文档对象模型,浏览器会将XML文档解析成文档对象模型
  1. 用例
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
AJAX (Asynchronous JavaScript and XML) 是一种用于前后数据交互的技术。它允许在不刷新整个页面的情况下,通过异步方式向服务器发送请求并获取响应数据。 使用 AJAX 可以实现以下功能: 1. 发送异步请求:通过 JavaScript 创建 XMLHttpRequest 对象,并使用该对象发送 HTTP 请求到服务器。 2. 处理响应数据:一旦服务器返回响应,可以通过回调函数处理返回的数据。常见的数据格式包括 XML、JSON 或纯文本。 3. 更新页面内容:根据服务器返回的数据,可以使用 JavaScript 动态更新页面内容,而不需要刷新整个页面。这样可以提升用户体验并减少网络流量。 以下是一个简单的 AJAX 示例代码: ```javascript // 创建 XMLHttpRequest 对象 var xhr = new XMLHttpRequest(); // 指定请求的方法和 URL xhr.open('GET', 'https://api.example.com/data', true); // 设置回调函数处理响应 xhr.onreadystatechange = function() { if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) { // 处理服务器返回的数据 var response = xhr.responseText; console.log(response); // 更新页面内容 document.getElementById('result').innerHTML = response; } }; // 发送请求 xhr.send(); ``` 在上述示例中,我们使用 AJAX 发送了一个 GET 请求到 `https://api.example.com/data`,并设置了一个回调函数来处理服务器返回的数据。在回调函数中,我们将返回的数据打印到控制台,并将其更新到 id 为 `result` 的 HTML 元素中。 通过 AJAX,前可以与后进行实时的数据交互,从而实现更加动态和响应式的用户界面。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值