node.js入门笔记
文章目录
1.Node.js
1.1 JavaScript开发弊端
JavaScript使用时存在两大问题,文件依赖和命名冲突
1.2 node.js模块化开发规范
- node.js规定一个JavaScript就是一个模块,模块内部定义的变量和函数默认情况下在外部无法得到
- 模块内部可以使用exports对象进行成员导出,使用require方法导入其他模块
1.3 module.exports和exports
两者相等,指向同一个引用地址,如果module.exports和exports都进行了修改,最终以module.exports为准
2.系统模块
2.1 什么时系统模块
node运行环境提供的API,因为这些Api都是以模块化方式进行开发的,所以又称node运行环境的api为系统模块
2.2 系统模块fs文件操作
node中的回调函数都是错误函数,第一个参数为报错信息,第二个时数据,如果没有错,第一个参数为null
读取文件内容
fs.readFile(‘文件路径/文件名称’,[编码格式],callback)
// 导入文件模块
const fs = require('fs');
// node,读写文件也有同步和异步的接口
fs.readFile('hello.txt', {
flag: 'r',
encoding: "utf-8"
},(err,data)=>{
if(err){
console.log(err)
}else{
console.log(data)
}
})
console.log(content);
写入文件内容
fs.writeFile(“文件按路径/文件名称”,‘数据’,callback)
// 导入文件模块
const fs = require('fs');
fs.writeFile('./demo.txt','即将写入的内容',err=>{
if(err!=null){
console.log(err);
return;
}
console.log("文件内容写入成功")
})
2.3 系统模块path路径操作
为什么要路径拼接
- 不统操作系统的路径分隔符不统一
- /public/uploads/avatar
- Windows上是\ 反斜杠
- Linux上是/
路径拼接语法
path.join(“路径”,“路径”,…)
const path = require("path")
const finalPath = path.join("public",'uploads','avatar')
console.log(finalPath)//public\uploads\avatar
2.4相对路径vs绝对路径
- 大多数情况下使用绝对路径,因为相对路劲有时候相对的是命令行工具的当前工作目录
- 在读取文件或设置文件路劲都会选择绝对路劲
- 使用__dirname获取当前文件所在的绝对路径
// 拼接成绝对路径
const path = require('path')
let info2 = path.join(__dirname,'sxt','qianduan')
2.5系统模块http创建网站服务器模块
// 创建网站服务器模块
const http = require("http")
// app对象就是网站服务器对象
const app = http.createServer()
// 当客户端有请求来的时候
app.on("request", (req, res) => {
//req为请求的,res为响应
// 根据请求执行对应的事件
})
// 监听端口
app.listen(3000)
console.log("服务器端口已启动,使用localhost:3000访问");
2.6系统模块url解析地址模块
const url = require('url')
//url.parse(要解析的url地址,true(将查询参数(query)解析成对象形式))
let {query,pathname} = url.parse(req.url,true)
2.7系统模块querystring转化参数为对象
const querystring = require("querystring")
app.on("request", (req, res) => {
// post 参数是通过事件的方式接受的
//data 当请求参数传递的时候触发data事件
//end 当参数参数完成时触发end事件
let postParams = ''
req.on("data",params=>{
// 将获取到的params参数拼接在一起
postParams += params
})
req.on('end',()=>{
let queryPa = querystring.parse(postParams)
console.log(querystring.parse(postParams));
console.log(queryPa.username);
})
res.end('ok')
})
3 获取第三方模块
npm(node package manager):node 的第三方模块管理工具
- 下载:npm install 模块名称
- 卸载:npm unintall package模块名称
全局安装和本地安装
- 命令行工具:全局安装
- 库文件:本地安装
3.1第三方模块nodemon
nodemon是一个命令行工具,用于辅助项目开发
在node。js中,每次修改文件都要在命令行工具中重新执行,很繁琐
使用步骤
- npm install nodemon -g
- 在命令行工具中用nodemon命令替代node命令 (nodemon index.js 执行文件,会在终端挂起,代码修改时保存会自动重新执行)
3.2第三方模块nrm
nrm: npm下载地址切换工具
npm默认的下载地址在国外,国内下载速度慢
使用步骤
- npm install nrm -g下载他
- 查询可用下载地址列表nrm ls
- 切换npm下载地址 nrm use 下载地址名称(nrm use taobao)
3.3第三方模块Gulp
基于node平台开发的前端构建工具
将机械化操作编写成任务,想要执行机械化操作时执行一个命令行命令任务就能自动执行
Gulp能做什么
- 项目上线,html,css,js文件压缩
- 语法转换(es6,less…)
- 公共文件抽离
- 修改文件浏览器自动刷新
Gulp使用
- 使用npm install gulp
- 在项目根目录简历gulpfile.js文件
- 重构项目的文件夹结构src目录放置源代码文件,dist目录放置构建后的文件
- 在gulpfile.js文件编写任务
- 在命令行工具中执行gulp任务
Gulp提供的方法
- gulp.src() 获取文物要处理的文件
- gulp.dest() 输出文件
- gulp.task() 建立gulp任务
- gulp.watch() 监控文件的变化
基本使用
npm install gulp-cli -g
终端执行gulp 任务名(task) 执行任务
// 不做异步处理或加dom,会报错The following tasks did not complete: first Did you forget to signal async completion?
// 引用gulp模块
const gulp = require("gulp")
// 使用gulp.task建立任务
// 1. 任务的名称
// 2. 任务的回调函数
// gulp.task("first", async() => {
// console.log("第一个gulp任务执行了");
// // 1. 使用gulp.src 获取要处理的文件
// await gulp.src("./src/css/index.css")
// .pipe(gulp.dest('dist/css'))
// })
//gulp 4.0 的任务函数中,如果任务是同步的,需要使用 done 回调。这样做是为了让 gulp 知道你的任务何时完成。
gulp.task("first", (done) => {
console.log("第一个gulp任务执行了");
// 1. 使用gulp.src 获取要处理的文件
gulp.src("./src/css/index.css")
.pipe(gulp.dest('dist/css'))
done()
})
Gulp插件
- gulp-htmlmin:html文件压缩 html文件压缩使用方法
- gulp-csso :压缩css
- gulp-babel :JavaScript语法转化
- gulp-less:less语法转化
- gulp-uglify:压缩混淆JavaScript
- gulp-file-include 公共文件包含
- browsersync 浏览器实时同步
npm install 插件名
引入插件,看官网插件使用
gulp-file-include使用方法:
- 吧公共代码提取出来放置到common文件夹
- 源代码处使用@@include("./common/xxx")引入
- gulpfile.js中使用插件即可
4. package.json
node_modules文件夹问题:
1.文件过多,将项目整体拷贝给别人,传输速度太慢
2. 复杂的模块依赖关系需要被记录,确保模块的版本和当前保持一直
项目根目录npm init -y初始化package.json文件夹
下载的依赖都会记录在dependencies中,只需执行npm install就能把需要的依赖下载好
npm install 所有依赖都下载好
npm install product 只下载项目依赖
项目依赖
- 在项目开发阶段和现上运营阶段,都需要依赖的第三方包,称为项目依赖,会记录在dependencies
// package.json里的
"dependencies": {
"formidable": "^1.2.2",
"mime": "^2.4.6"
}
开发依赖
- 在项目开发阶段需要,吸纳上运营阶段不需要依赖的第三方包,成为开发依赖
- 使用npm install 包名 --save-dev命令将包添加到package.json文件的devDependencies字段中
// package.json
//scripts别名 "build"命令替换原来的nodemon app.js命令。npm run build执行
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build","nodemon app.js"
},
"devDependencies": {
"gulp": "^4.0.2"
}
package-lock.json文件的作用
- 锁定包的版本,确保再次下载时不会因为包版本不同而产生问题
- 加快下载速度,因为该文件中已经记录项目所依赖第三方包的树状结构和包的下载地址,重新安装时只需下载即可,不需要额外的工作
5.node.js模块加载机制
5.1模块查找规则-当模块拥有路径但没有后缀时
require(’./find.js’)
require(’./find’)
- require方法根据模块路径查找模块,如果时完整路径,直接引入模块
- 如果模块后缀省略,先找同名js文件再找文件夹
- 如果找到了同名文件夹,找文件夹中的index.js
- 如果文件夹中没有index.js就回去当前文件夹中的package.js文件中查找main选项中的入口文件
- 如果找指定的入口文件不存在或者没有指定入口文件就会报错,模块没有找到
5.2模块查找规则-当模块没有路径且没有后缀时
require(‘find’)
- node.js会假设他是系统模块
- node.js会去node_modules文件夹中
- 首先是否有该名字的js文件
- 再看是否有该名字的文件夹
- 如果时文件夹看是否有index.js
- 如果没有index.js查看该文件夹中package.json中的main选项确定模块入口文件
- 找不到报错
6.服务器端基础概念
URL组成
传输协议:服务器ip或域名:端口/资源所在位置表示,一般端口默认8080会省略
http://www.itcast.cn/index.html
http: 超文本传输协议,提供了一种发布和接收html页面方法
创建web服务器(http模块)
// 创建网站服务器模块
const http = require("http")
// app对象就是网站服务器对象
const app = http.createServer()
// 当客户端有请求来的时候
app.on("request",(req,res)=>{
//req为请求的,res为响应
res.end('<h2>hello user</h2>')
})
// 监听端口
app.listen(3000)
console.log("服务器端口已启动,使用localhost:3000访问");
HTTP协议
HTTP协议概念
超文本传输协议,规定如何从网站服务器传输文本到本地浏览器,基于客户端服务器架构工作,是客户端(用户)和服务器端(网站)请求和应答标准
报文
在HTTP请求和响应的过程中传递的数据块就叫报文,包括要传送的数据和一些附加信息,并且要遵守好规定的格式
客户端->服务器端 请求报文(请求方式(post),请求地址)
服务器端-> 客户端 响应报文(内容类型(text/html),内容长度)
请求报文
- 请求方式
- get 请求数据
- post 发送数据
- 请求地址
app.on("request", (req, res) => {
// 获取请求方式,console.log(req.method)
// 获取请求地址
req.url
// 获取请求报文
req.headers
}
if(req.url=='/index'||req.url=="/"){
res.end("<h2>welcome to homepage欢迎来到首页</h2>")
}
else if(req.url=='/list'){
res.end("welcome to list")
}
else{
res.end("no find")
}
if(req.method=='GET'){
res.end("GET请求")
}else if(req.method==='POST')
{
res.end("post请求")
}
}
响应报文
- HTTP状态码
- 200 请求成功
- 404请求资源没有找到
- 500 服务器端错误
- 400 客户端请求有语法错误
- 内容类型
- text/html
- text/css
- application/javascript
- image/jpeg
- application/json
http.on("request",(req,res)=>{
res.writeHead(200,{
// 默认content-type ,纯文本
// "content-type":'text/plain'
// 设置为text/html就能看到返回的html代码效果了
"content-type":'text/html;charset=utf8'
})
if(req.url=='/index'||req.url=="/"){
res.end("<h2>welcome to homepage欢迎来到首页</h2>")
}
}
请求参数
客户端向服务器发送请求携带的信息。
GET请求参数
- 参数被放置在浏览器地址栏中 如:http://localhost:3000/?name=zhangsan&age=20
POST请求参数
- 参数被放置在请求体中进行传输
- 获取post参数需要使用data事件和end事件
- 使用querystring系统模块将参数转换成对象格式
// 创建网站服务器模块
const http = require("http")
//处理请求参数模块 querystring,querystring.parse解析成对象形式
const querystring = require("querystring")
// app对象就是网站服务器对象
const app = http.createServer()
// 当客户端有请求来的时候
app.on("request", (req, res) => {
// post 参数是通过事件的方式接受的
//data 当请求参数传递的时候触发data事件
//end 当参数参数完成时触发end事件
let postParams = ''
req.on("data",params=>{
// 将获取到的params参数拼接在一起
postParams += params
})
req.on('end',()=>{
let queryPa = querystring.parse(postParams)
console.log(querystring.parse(postParams));
console.log(queryPa.username);
})
res.end('ok')
})
// 监听端口
app.listen(3000)
console.log("服务器端口已启动,使用localhost:3000访问");
路由
路由是由指客户端请求地址与服务器程序代码的对应关系,即请求什么响应什么
const http = require("http")
const url = require("url")
const app = http.createServer()
app.on("request", (req, res) => {
res.writeHead(200, {
"content-type": 'text/html;charset=utf-8'
})
const method = req.method.toLowerCase()
const pathname = url.parse(req.url).pathname
if (method == 'get') {
if (pathname == '/' || pathname=='/index') {
res.end("<h1>欢迎来到首页")
} else if (pathname == '/list') {
res.end("<h1>欢迎来到列表页")
} else {
res.end("<h1>404网页已丢失")
}
}
})
app.listen(3000)
console.log("服务开启,访问http://localhost:3000");
静态资源
服务器端不需要处理,可以直接响应给客户端的资源就是静态资源,如:CSS,JavaScript,image文件
动态资源
相同的请求地址不统的响应资源,这种资源就是动态资源
http://www.itcast.cn/article?id=1
http://www.itcast.cn/article?id=2
获取静态资源类型插件 mime
mime.getType(path)
const http = require("http")
const url = require("url")
const path = require("path")
const fs = require("fs")
const mime = require("mime")
const app = http.createServer()
app.on("request",(req,res)=>{
let pathname = url.parse(req.url).pathname
pathname = pathname=="/"?'/3d.html':pathname
const relPath = path.join(__dirname,"public",pathname)
// 获取资源类型
const type = mime.getType(relPath)
// console.log(relPath);
fs.readFile(relPath,(err,data)=>{
if(err!=null){
res.writeHead("404",{
"content-type":"text/html;charset=utf-8"
})
res.end("<h1>访问失效")
}
else{
res.writeHead("200",{
"content-type":type
})
res.end(data)
}
})
})
app.listen(3000)
console.log("服务器已启动");
node.js异步编程
同步api,异步api
同步api:只有当前api执行完成,才能继续下一个api
异步api: 当前api的执行不会阻碍后续代码的执行
同步API异步API区别 (获取返回值)
同步API可以从返回值拿到api结果,异步不行
// 同步
function sum(n1,n2){
return n1+n2
}
const result = sum(10,20) // 30
function getMsg(){
setTimeout(function(){
return{
msg:'hello node.js'
}
},2000)
// return undefined 函数默认会有个返回undefined
}
const msg = getMsg()
// 执行完异步函数时,msg已经输出的
console.log(msg); //undefined
回调函数
自己定义函数让别人调用
// 自己定义函数让别人调用
function getData(callback){}
//getdata函数调用
getData(()=>{})
同步api和异步api的区别(代码执行顺序)
同步api从上到下一次执行,前面的代码会阻塞后面的代码执行
for(var i=0;i<10000;i++){
console.log(i)
}
console.log("for后面的代码")
// 会先执行for循环在输出
异步api不会等待api执行完成后再向下执行代码
console.log("代码开始执行")
setTimeout(()=>{console.log("2秒后执行")},2000)
setTimeout(()=>{console.log("0秒后执行")},0)
console.log("代码结束执行")
// 结果:代码开始执行,代码结束执行,0秒后执行,2秒后执行
代码执行顺序
先执行同步代码,遇到异步代码时先放置回调函数队列,同步代码执行完毕后,根据异步代码执行区去找到对应的回调函数队列里的异步函数,将其执行
node.js异步api
fs.readFile('./demo.txt',(err,result)=>{})
var server = http.creteServer()
server.on('request',(req,res)=>{})
promise解决异步函数回调地狱问题
let promise = new Promise((resolve,reject)=>{
setTimeout(()=>{
if(true){
resolve("成功了")
}
else{
reject("失败了")
}
},2000)
})
promise.then(result=>console.log(result))
.cath(error=>console.log(error))
异步函数 async/await
async关键字
- 普通函数前加async关键字,变成异步函数
- 异步函数默认返回promise对象
- 在异步函数内部使用return关键字进行结果返回 结果会被包裹在promise中,return关键字替代了resolve方法
- 在异步函数内部使用throw关键字抛出程序异常
- 调用异步函数再链式调用then方法获取异步函数执行结果
- 调用异步函数再链式调用catch方法获取异步函数执行的错误信息
async function fn(){
// async 会把函数编程异步函数,返回promise对象,return为原来的resolve方法
return "成功"
}
fn().then((result)=>{
console.log(result);
})
async function f2(){
// throw为原来的reject方法
throw "错误"
}
f2().then((result)=>{
console.log(result);//不执行
}).catch(err=>{
console.log(err);//返回了错误
})
await
- await只能出现在异步函数中
- await promise await后面只能写promise对象
- await关键字可暂停异步函数向下执行,直到promise返回结果
相当于封装了一个new Promise()方法
let fs = require("fs")
let promisify = require("util").promisify
// 改造现有异步函数api让其返回promise对象 从而支持异步函数语法
let readFile = promisify(fs.readFile)
async function run(){
let r1 = await readFile("1.txt","utf-8")
let r2 = await readFile(r1+".txt","utf-8")
let r3 = await readFile(r2+".txt","utf-8")
}
run()
数据库
为什么使用数据库
- 动态网站的数据都是存储在数据库中
- 数据库可以用来持久存储客户端通过表单收集的用户信息
- 数据库软件本身可以对软件进行高效管理
数据库相关概念
在一个数据库软件中可以包含多个数据库,在每个数据仓库中可以包含多个数据集合,每个数据集中中可以包含多条文档(具体数据)
术语 | 解释说明 |
---|---|
database | 数据库,mongoDB数据库软件可以建立多个数据库 |
collection | 集合,一组数据的集合,可以理解为JavaScript中的数组 |
document | 文档,一条具体的数据,可以理解为JavaScript中的对象 |
field | 字段,文档中的属性名称,可以理解为JavaScript中的对象属性 |
使用mongoDB下载好mongodb后,进入bin文件,执行命令行 mongod --dbpath E:\web前端总学习\Node学习\软件\datapack 开启mongod,就可以使用图形化工具连接数据库了
node操作mongodb 下载第三方包 mongoose
连接数据库
// 没有playground数据库会自动创建
const mongoose = require("mongoose")
mongoose.connect("mongodb://localhost/playground",{useNewUrlParser: true,useUnifiedTopology: true})
.then(()=>{
console.log('数据库连接成功');
})
.catch(()=>{
console.log('连接失败');
})
创建集合
创建集合分为两步,一是对集合设定规则,二是创建集合,创建mongoose.Schema构造函数的实例即可创建集合
创建文档
创建文档实际就是插入数据
分两步:
- 创建集合实例
- 调用实例对象下的save方法将数据保存到数据库中
let mongoose = require("mongoose")
mongoose.connect("mongodb://localhost/playground",{useNewUrlParser: true,useUnifiedTopology: true})
.then(()=>{
console.log('数据库连接成功');
})
.catch(()=>{
console.log('连接失败');
})
// 设定集合规则
const courseSchema = new mongoose.Schema({
name:String,
author:String,
isPublished:Boolean
})
// 创建集合并应用规则,接收的是构造函数
// 第一个参数为集合名称,第二参数为集合规则,在compass中会集合名称会自动加上s和变小写
const Course = mongoose.model("Course",courseSchema) //courses
// 创建文档
const course = new Course({
name:'ljx2',
author:'ali',
isPublished:true
})
// 将文档插入到数据库
course.save()
向集合中插入数据的第二种方法
const Course = mongoose.model("Course",courseSchema)
Course.create({name:'ljx3',author:'第二种方法',isPublished:true},(err,data)=>{
console.log(err);
console.log(data);
})
Course.create({name:'ljx3',author:'第二种方法',isPublished:true})
.then(data=>console.log(data))
.catch(err=>console.log(err))
导入数据
mongoimport -d 数据库名称 -c 集合名称 --file 要导入的文件
导入数组json时
使用mongoimport -d 数据库名称 -c 集合名称 --jsonArray 要导入的文件
查询文档
find()根据条件查找文档(条件为空则查找所有文档)
Course.find().then(result=>{console.log(result)})
// 查询用户集合中的所有文档
Course.find().then(result=>{console.log(result);})
// 根据条件查询
Course.find({name:"ljx"}).then(result=>{console.log(result);})
//返回文档(数组的形式)
[
{
_id: 5f9e95da33c7093398444d30,
name: 'ljx',
author: 'ali',
isPublished: true,
__v: 0
}
]
findOne() 返回一个文档
//根据条件查询文档
Course.findOne({name:"ljx"}).then(result=>{console.log(result);})
// 返回文档
{
_id: 5f9e95da33c7093398444d30,
name: 'ljx',
author: 'ali',
isPublished: true,
__v: 0
}
- 匹配大小于
- $gt 大于 KaTeX parse error: Expected '}', got 'EOF' at end of input: … 小于 find({age:{gt:20,$lt:50}}).then(result=>{console.log(result)})
- 匹配包含$in
- 选择要查询的字段
字段前面加-代表不查询
select(‘name age -_id’) - 排序
sort(‘age’)
// 查询用户集合中的所有文档
test.find().then(result=>{console.log(result);})
// 根据大小与查询
test.find({age:{$gt:15,$lt:20}}).then(result=>console.log(result))
// 匹配包含
test.find({hobbies:{$in:["足球"]}}).then(result=>console.log(result))
// 需要查询的字段
test.find().select('name age -_id').then(result=>console.log(result))
// 排序,根据年龄升序排序,在字段名前加-降序排序
test.find().sort('-age').then(result=>console.log(result))
// skip(n)跳过n条数据,limit限制查询数量(分页的时候会用到)
test.find().skip(2).limit(3).then(result=>console.log(result))
删除文档
findOneAndDelete()
- 找到一条数据并删除
- 返回删除文档
- 如果匹配到多个文档,删除第一个匹配的文档
test.findOneAndDelete({name:'ljx2'}).then(result=>{console.log(result);})
deleteMany()
- 删除多个如果参数为空,会删除全部,返回一个对象,n删除条数,ok为1删除成功
test.deleteMany({hobbies:{$in:['排球']}}).then(result=>console.log(result))
更新文档
// 更新单个文档
// test.updateOne({查询条件},{要修改的值})
// test.updateOne({name:'ljx6'},{name:"梁非凡"}).then(result=>console.log(result))
// 更新多个文档
// test.updataMany({},{})
test.updateMany({age:{$gt:10}},{age:100}).then(result=>console.log(result))
mongoose验证
在创建集合规则时,可以设置当前字段的验证规则,验证失败则输入插入失败
- required:true 必传字段
- minlength:[2,“字符串长度不能超过2”],
- maxlength:[6,“字符串长度不能超过6”],
- trim:true
- min: 最小值
- max:最大值
- default: Date.now 设置默认时间
- enum:[‘html’,‘css’,‘node.js’] 枚举,只能传入这些值
- validate: 自定义验证器
let mongoose = require("mongoose")
mongoose.connect("mongodb://localhost/playground", {
useNewUrlParser: true,
useUnifiedTopology: true
})
.then(() => {
console.log('数据库连接成功');
})
.catch(() => {
console.log('连接失败');
})
// 设定集合规则
const postScheme = new mongoose.Schema({
title: {
type: String,
required: [true, '请输入标题'],
minlength: [2, "最小长度不能超过2"],
maxlength: [6, "最大长度不能超过6"],
// 去除空格
trim: true
},
age: {
type: Number,
// 最小的值
min: 18,
// 最大的值
max: [100, "最大年龄不能超过100"]
},
publishDate: {
type: Date,
// 设置默认值为当前时间
default: Date.now
},
category: {
type: String,
// 枚举,列举出当前字段可以拥有的值
enum: {
values: ['html', 'css', 'javascript', 'node.js'],
//返回错误的信息
message: '分类名称不可用'
}
},
// 自定义验证规则
author: {
type: String,
validate: {
validator: v => {
// 返回布尔值
// true验证成功
// false验证失败
// v 要验证的值
return v && v.length > 4
},
message: '传入的值不符合验证规则'
}
}
})
const post = mongoose.model("Post", postScheme)
post.create({
title: ' aaaaa ',
age: 99,
category: 'css',
author: '吾无异于曹贼'
}).then(result => {
console.log(result);
})
集合关联
通过不同集合的数据之间是有关系的,例如文章信息何用户信息存储在不同集合中,但文章是某个用户发布的,要查询文章的所有信息包括发布用户,需要用到集合关联
- 使用id对集合进行关联
- 使用populate方法进行关联集合查询
const mongoose = require("mongoose")
mongoose.connect("mongodb://localhost/playground", {
useNewUrlParser: true,
useUnifiedTopology: true
}).then(() => {
console.log('数据库连接成功');
})
.catch(() => {
console.log('连接失败');
})
// 用户规则
const userSchema = new mongoose.Schema({
name:{
type:String,
required:[true,"请输入名字"]
},
age:{
type:Number
}
})
// 文章规则
const postSchema = new mongoose.Schema({
title:{
type:String
},
author:{
type:mongoose.Schema.Types.ObjectId,
ref:'User'
}
})
// 用户集合
const user = mongoose.model('User',userSchema)
// 文章集合
const Post = mongoose.model("Post",postSchema)
// 创建用户
user.create({name:'ljx',age:'18'}).then(result=>console.log(result))
// 创建文章
Post.create({title:'发布的文章题目',author:'5f9f959b8ee81514206ea593'}).then(result=>console.log(result))
// Post.find().then(result=>console.log(result))
// 联合查询
Post.find().populate('author').then(result=>console.log(result))
案例:用户信息的增删查改 user
- 搭建网站服务器,实现客户端与服务器端的通信
- 连接数据库,创建用户集合,向集合中插入文档
- 当用户访问/list时,将所有用户信息查询出来
- 当用户信息和表格html进行拼接并将拼接结果响应回客户端
- 当用户访问/add时,呈现表单页面,并实现添加用户信息功能
- 当用户访问/modify时,呈现修改页面,并实现修改用户信息功能
- 当用户访问/delete时,实现用户删除功能
模板引擎
模板引擎是第三方模块。
让开发者更加友好地拼接字符串,是项目代码更加清晰,更加易于维护
第三方模块art-template 模板引擎
- npm install art-template
- const template = require(‘art-template’)引入模板引擎
- 告诉模板引擎要拼接的数据和模板在哪const html = template(‘模板路径’,数据)
模板语法
- art-template 同时支持两种语法:标准语法和原始语法
- 标准语法可以让模板更容易读写,原始语法具有强大的逻辑处理能力
标准语法:{{数据}}
原始语法:<%=数据%>
输出
将某项数据输出在模板中,标准语法和原始语法如下:
- 标准语法:{{数据}}
- 原始语法:<%=数据%>
原文输出
如果数据中携带html标签,默认模板引擎不会解析标签,会将其转义后输出
- 标准语法:{{@数据}}
- 原始语法:<%-数据%>
条件判断
标准语法{{if 条件 }}内容{{else if 条件}}内容{{else}}内容
{{/if}}
原始语法<%if(条件){%>内容
<%}else if(条件){%>内容
<%}>else {%>内容
<%}%>
<!-- 标准语法 -->
{{if age > 18}}
年龄大于18
{{else if age < 15 }}年龄小于15
{{else}}
年龄不符合要求
{{/if}}
// 原始语法
<% if(age>18){%>
年龄大于18
<%} else if(age<15) {%>年龄小于15
<%} else {%>年龄不符合要求
<%}%>
循环
- 标准语法:{{each 数据}}{{/each}}
- 原始语法:<%for(){%><%}%>
<!--标准语法-->
<ul>
{{each user}}
<li>{{$index}}</li>
<li>{{$value.name}}</li>
<li>{{$value.age}}</li>
<li>{{$value.sex}}</li>
{{/each}}
</ul>
<!--原始语法-->
<ul>
<%for(var i = 0;i < user.length;i++){%>
<li>
<%= i%>
<%= user[i].name%>
<%= user[i].age%>
<%= user[i].sex%>
</li>
<%}%>
</ul>
子模版
使用子模版可以将网站的公共区块(头部,底部)抽离到单独的文件中。
- 标准语法:{{include ‘模板’}}
- 原始语法:<%include(‘模板’)%>
{{include './common/header.art'}}
<%include('./common/footer.art')%>
模板继承
使用模板继承可以将网站html骨架抽离到单独的文件中,其他页面模块可以继承骨架文件。
公共模块 layout.art 将html骨架抽离出来,并在需要填充的位置使用{{block ‘name’}}{{/block}}说明
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{{block 'title'}}{{/block}}
{{block 'link'}}{{/block}}
</head>
<body>
{{block 'content'}}{{/block}}
</body>
</html>
继承模板 05.art
extent '路径’继承模板
{{block}}内容{{/block}}填充模板
{{extend './common/layout.art'}}
{{block 'title'}}
<title>测试标题</title>
{{/block}}
{{block 'content'}}
{{msg}}
{{/block}}
{{block 'link'}}
<link rel="stylesheet" href="">
{{/block}}
模板配置
- 向模板导入变量 template.defaults.imports.变量名=变量值
- 设置模板根目录template.defaults.root = 模板目录
- 设置模板默认后缀 template.defaults.extname=’.art’
// 配置模板根目录
template.defaults.root = path.join(__dirname,"views")
// 导入模板变量
template.defaults.imports.dateFormat = dateFormat
// 配置模板默认后缀
template.defaults.extname = '.html'
const html = template(viewsPath, {
time:new Date()
})
// 没有后缀时自动默认.html后缀
const html2 = template('06',{})
console.log(html);
console.log(html2);
第三方模块dateformat时间格式化
案例
制作流程
- 建立项目文件夹并生成项目描述文件
- 创建网站服务器实现客户端和服务端通信
- 连接数据库并根据需求设计学员信息表
- 创建路由并实现页面模板呈现
- 实现静态资源访问
- 实现学生信息添加功能
- 实现学生信息展示功能
案例: 第三方模块router
功能:实现路由
使用步骤:
- 获取路由对象
- 调用路由对象提供的方法创建路由
- 启动路由
const getrouter = require("router")
const router = getrouter()
router.get('/add',(req,res)=>{
res.end()
}
app.on('request',(req,res)=>{
router(req,res)
})
第三方模块 serve-static
功能:实现静态资源访问服务
步骤:
- 引入serve-static模块并获取创建静态资源服务功能的方法
- 调用方法创建静态资源服务并指定静态资源服务目录
- 启用静态资源服务功能
const serveStatic = require("serve-static")
// 实现静态访问服务
const serve = serveStatic(path.join(__dirname,'public'))
server.on('request',()=>{
serve(req,res)
})
server.listen(3000)
添加学生信息功能步骤分析
- 再模板表单种指定请求地址和请求方式
- 为每一个表单添加name属性
- 添加实现学生信息功能路由
- 接收客户端传递过来的学生信息
- 将学生信息添加到数据库中
- 将页面重定向到学生信息列表页面
学生信息列表页面分析
- 从数据库中将所有学生的信息查询出来
- 通过模板引擎将学生信息和HTML模板进行拼接
- 将拼接好的html模块响应给客户端
Express框架
Express是一个基于node平台的web应用开发框架,它提供了一系列的强大特性,帮助你创建各种web应用。
使用npm install express下载
express框架特性
- 提供方便简洁的路由定义方式
- 对获取HTTP请求参数进行了简化处理
- 对模板引擎支持程度高,方便渲染动态HTML页面
- 提供了中间件机制有效控制HTTP请求
- 拥有大量第三方中间件对功能进行扩展
中间件
中间件就是一堆方法,可以接收客户端发来的请求,可以对请求做出响应,也可以将请求继续交给下一个中间件继续处理
中间件主要由两部分构成,中间件方法以及请求处理函数
中间件方法由express提供,负责拦截请求,请求处理函数由开发人员提供,负责处理请求
如:
app.get('请求路径','处理函数') //接收并处理get请求
app.post('请求路径','处理函数') //接收并处理post请求
可以针对同一个请求设置多个中间件,对同一个请求进行多次处理.
默认情况下,请求从上到下以此匹配中间件,一旦匹配成功,终止匹配.
可以调用next方法将请求的控制权交给下一个中间件,直到遇到结束请求的中间件
app.get('./request',(req,res,next)=>{
req.name='张三';
next()
})
app.get('./request',(req,res)=>{
res.send(req.name)
})
app.use中间件用法
app.use 匹配所有的请求方式,可以直接传入请求处理函数,代表接收所有的请求。
注意:app.use中间件放在其他前面
app.use((req,res,next)=>{
console.log("消息经过了use中间件");
next()
})
// 加上参数地址时,表示只有该路径会触发该中间件
app.use('/request',(req,res,next)=>{
res.send("消息经过了request")
})
中间件应用
- 路由保护,客户端在访问需要登录的页面时,可以先使用中间件判断用户登陆状态,未登录则拦截请求,直接响应禁止用户进入需要登录的页面
- 网站维护公告,在所有路由的最上面定义接收所有请求的中间件,直接为客户端做出响应,网站正在维护中
- 自定义404页面
const app = express()
// 网站公告,直接响应公告内容
app.use((req,res,next)=>{
res.send("网站维护中。。。请于xxx时间段再访问")
})
// 拦截未登录用户
app.use((req,res,next)=>{
let isLogin = true
if(isLogin){
next()
}else{
res.send("请登录后再访问页面")
}
})
app.get('/request',(req,res,next)=>{
res.send("欢迎来到request页")
})
// 自定义404页面,放在最后面,等上面的中间件先判断完有没有符合的
app.use((req,res,next)=>{
res.status(404).send("网页不存在")
})
错误处理中间件
程序执行的过程中,不可避免的会出现一些无法预料的错误,比如文件读取失败,数据库连接失败,错误处理中间件是一个集中处理错误的地方
当程序出现错误时,调用next()方法,将错误信息传递给next()方法,即可触发错误处理中间件
app.use((err,req,res,next)=>{
res.status(500).send(err.message)
})
// 模拟异步请求发生错误,异步api的错误都是通过回调获取的
app.get('/list',(req,res,next)=>{
fs.readFile('./2文件清单.txt','utf8',(err,result)=>{
if(err!=null){
// next带参数则作为程序错误
next(err)
}else{
res.send(result)
}
})
})
// 模拟同步请求错误
app.get('/index',(req,res)=>{
throw new Error('程序发生了未知错误')
})
app.get("/request",(req,res)=>{
res.send('正常访问')
})
// 错误中间件
app.use((err,req,res,next)=>{
res.status(500).send(err.message)
})
捕获错误
再node.js中,异步api的错误信息都是通过回调函数获取的,支持promise对象的异步api发生错误可以通过catch方法获取。
try catch可以捕获异步函数以及其他同步代码再执行过程中发生的错误,但是不能其他类型的api(如回调函数)发生错误
const promisify = require("util").promisify
const readFile = promisify(fs.readFile)
// 模拟异步promise对象的异步api发生错误
app.get('/list',async (req,res,next)=>{
try {
await readFile('./2文件清单.txt')
} catch (error) {
next(error)
}
})
// 错误中间件
app.use((err,req,res,next)=>{
res.status(500).send(err.message)
})
Express请求处理
模块化路由
基础用法
const express = require("express")
// 创建网站服务器
const app = express()
// 创建路由对象
const home = express.Router()
// 为路由对象匹配添加路径
app.use('/home',home)
// 在home路由下创建二级路由
home.get('/index',(req,res)=>{
res.send('欢迎来到首页')
})
app.listen(3000)
console.log('服务已启动');
构建模块化路由
home.js
const express = require("express")
const home = express.Router()
home.get('/index',(req,res)=>{
res.send('欢迎来到home首页')
})
module.exports = home
admin.js
const express = require("express")
const admin = express.Router()
admin.get('/index',(req,res)=>{
res.send("欢迎来到admin主页")
})
module.exports = admin
index.js
const express = require("express")
// 创建网站服务器
const app = express()
const home = require("./route/home")
const admin = require("./route/admin")
app.use('/home',home)
app.use('/admin',admin)
app.listen(3000)
console.log('服务已启动');
GET参数获取
Express框架使用req.query即可获取get参数,框架内部会将参数转换为对象并返回
const app = express()
app.get('/index',(req,res)=>{
res.send(req.query)
})
POST参数获取
Express接收post请求参数需要借助第三方包 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)=>{
// 获取post请求参数
console.log(req.body)
})
Express路由参数
// 此时需要通过http://localhost:3000/index/123/ljx 来访问
// 前面req.query获取的参数形式是http://localhost:3000/index?id=123&&name:ljx
app.get('/find/:id/:name',(req,res)=>{
res.send(req.params) //{"id":"123","name":"ljx"}
})
静态资源的处理
通过Express.static可以方便地托管静态文件
app.use(express.static('public')
const express = require("express")
const bodyParser = require("body-parser")
const path = require("path")
const app = express()
// 静态资源访问http://localhost:3000/css/list.css
// app.use(express.static(path.join(__dirname,'public')))
//静态资源访问http://localhost:3000/static/css/list.css
app.use('/static',express.static(path.join(__dirname,'public')))
app.listen(3000)
console.log("服务已启动");
express-art-template模板引擎
为了art-template和express框架配合使用而封装成的express-art-template
使用npm install art-template express-art-template 下载
// 当渲染后缀为art的模板时,使用expree-art-template
app.engine('art',require('expree-art-template'))
// 设置模板存放目录
app.set('views',path.join(__dirname,'views'))
// 渲染模板时 不写后缀 默认拼接art后缀
app.set('view engine','art');
app.get('/',(req,res)=>{
// 渲染模板
res.render('index')
})
const express = require('express')
const path = require("path")
const app = express()
// 1. 告诉express框架使用什么模板引擎渲染什么后缀的模板文件
// 1. 模板后缀
// 2. 使用的模板引擎
app.engine('art',require("express-art-template"))
// 2. 告诉express框架模板存放的位置 views为固定的配置名称
app.set("views",path.join(__dirname,'views'))
// 3. 告诉express框架模板的默认后缀是什么
app.set('view engine','art');
app.get('/index',(req,res)=>{
res.render('index',{
msg:'index page'
})
})
app.get('/list',(req,res)=>{
//render处理了以下内容
// 1. 拼接模板路径
// 2. 拼接模板后缀
// 3. 哪一个模板和哪一个数据进行拼接
// 4. 将拼接结果响应给客户端
res.render('list',{
msg:'list page'
})
})
app.listen(3000)
console.log("服务已启动");
app.locals对象
将变量设置在这,所有的模板都能使用这个数据
app.locals.users = [{
name:'张三',
age:14
},{
name:'李四',
age:20
}]
index.art
{{msg}}
<ul>
{{each users}}
<li>{{$value.name}}</li>
<li>{{$value.age}}</li>
{{/each}}
</ul>
index.js
const express = require('express')
const path = require("path")
const app = express()
// 1. 告诉express框架使用什么模板引擎渲染什么后缀的模板文件
// 1. 模板后缀
// 2. 使用的模板引擎
app.engine('art',require("express-art-template"))
// 2. 告诉express框架模板存放的位置 views为固定的配置名称
app.set("views",path.join(__dirname,'views'))
// 3. 告诉express框架模板的默认后缀是什么
app.set('view engine','art');
// 设置公共的对象
app.locals.users = [{
name:'张三',
age:14
},{
name:'李四',
age:20
}]
app.get('/index',(req,res)=>{
res.render('index',{
msg:'index page'
})
})
app.get('/list',(req,res)=>{
//render处理了以下内容
// 1. 拼接模板路径
// 2. 拼接模板后缀
// 3. 哪一个模板和哪一个数据进行拼接
// 4. 将拼接结果响应给客户端
res.render('list',{
msg:'list page'
})
})
app.listen(3000)
console.log("服务已启动");