Node 学习
Node.jsNode.js 介绍Node.js 是什么Node.js 是什么JavaScript运行时环境既不是语言,也不是框架,是一个平台Node.js 中的 JavaScript没有BOM、DOM,服务端不处理页面ECMAScript在 Node 中为 JavaScript 提供了一些服务器级别的API文件读写网络服务的构建网络通信http 服务...
Node.js 介绍
Node.js 是什么
-
JavaScript 运行时环境
-
通俗的讲,Node.js 是 JavaScript 的运行平台
既不是语言,也不是框架,是一个平台
Node.js 中的 JavaScript
- 没有BOM、DOM,服务端不处理页面
- ECMAScript
- 在 Node 中为 JavaScript 提供了一些服务器级别的API
- 文件读写
- 网络服务的构建
- 网络通信
- http 服务器
Node.js 官网内容:
-
Node.js is a JavaScript runtime built on Chrome’s V8 JavaScript engine.
- JavaScript 运行时
-
Node.js uses an event-driven,non-blocking I/O model that makes it lightweight and efficient.
- event-driven 事件驱动
- non-blocking I/O model 无阻塞IO模型(异步)
- lightweight and efficient 轻量和高效
-
Node.js package ecosystem,npm,is the largest ecosystem of open source libraries in the world.
- npm 是世界上最大的开源库生态系统
代码规范
GitHub Flavored Markdown,简称GFM
一级列表 推荐 -
二级列表 推荐 +
三级列表 推荐 *
当你采用无分号的代码风格的时候,需要注意以下情况:一行代码是以 以下开头的时候,需要在前面补上一个分号避免语法解析错误
- (
- [
- `
;(function () {
console.log('hello')
})
;['apple', 'banana'].forEach(function (item) {
console.log(item)
})
;`hello`.toString()
资源
- 《深入浅出 Node.js》
- 《Node.js 权威指南》
- JavaScript 标准参考教程(alpha):https://javascript.ruanyifeng.com
- Node 入门:https://www.nodebeginner.org/index-zh-cn.html PDF
- CNODE-新手入门:https://cnodejs.org/getstart
起步
安装
-
下载:https://nodejs.org/en/
-
安装:傻瓜式安装,一路
next
-
确认 Node 是否安装成功
查看 node 版本号:
node --version
或node -v
-
配置环境变量
文件读写 fs
浏览器中的 JavaScript 是没有文件操作能力的,但是 Node 中的 JavaScript 是有文件操作能力的
- fs 是 file-system 的简写,就是文件系统的意思
文件读取:
- 文件存储其实都是二进制数据(0 1),可以通过 toString 转换成我们认识的字符
let fs = require('fs')
/*
第一个参数:文件路径
第二个参数:回调函数
成功
data 读取到的数据
error null
失败
data undefined
error 错误对象 */
fs.readFile('./datas.txt', funtion (error, data) {
// data -> <Buffer 68 65 6c 6c 6f 20 6e 6f 64 65 6a 73 0d 0a>
if (error) {
console.log('读取文件失败')
} else {
console.log(data.toString())
}
})
文件写入:
/*
第一个参数:文件路径
第二个参数:文件内容
第三个参数:回调函数
成功
文件写入成功
error null
失败
文件写入失败
error 错误对象 */
let seq = '我是写入文件的信息'
fs.writeFile('./hello.md', seq, function (error) {
if (error) {
console.log('文件写入失败')
} else {
console.log('文件写入成功')
}
})
服务器 http
http 这个模块职责:帮你创建编写的服务器
-
当客服端请求过来,就会自动触发服务器的
request
请求事件,然后执行第二个参数(回调处理函数) -
回调函数有两个参数:
request 用来获取客户端的一些请求信息,例如请求路径
response 用来给客户端发送响应消息。响应内容只能是二进制数据或字符串数字,对象、数组、布尔值都无法响应
-
response 对象有一个方法:write 可以用来给客户端发送响应数据
write 可以使用多次,但是最后一定要使用 end 来结束响应,否则客户端会一直等待
-
// 1. 加载http核心模块
let http = require('http')
// 2. 使用http.createServer()方法创建一个Web服务器
let server = http.createServer()
// 3. 服务器要做的事
server.on('request', function (request, response) {
console.log('收到客户端的请求了,请求路径是:' + request.url)
/* response.write('hello')
response.end() */
//推荐使用
response.end('hello')
let url = req.url
if (url === '/') {
res.end('index page')
}else if (url === '/login') {
res.end('login page')
}else if (url === '/products') {
let products = [{
name: '苹果 X',
price: 8888
}]
//JSON.parse 可以转换成对象
res.end(JSON.stringify(products))
}else {
res.end('404 Not Found')
}
})
//4. 绑定端口号,启动服务器
server.listen(3000, function () {
console.log('running at: http://localhost:3000/')
})
Node 中的模块系统
-
ECMAScript 语言
和浏览器一样,但是没有 BOM 和DOM
-
核心模块
fs
文件操作模块http
创建和管理服务模块url
解析 url 路径模块path
路径处理模块os
操作系统信息模块
-
第三方模块
-
art-template
必须通过 npm 下载才能使用
-
-
用户自定义模块
用户自己编写的文件模块,相对路径必须加 ./ (可以省略后缀名)
require('./b,js')
//a.js let bExports = require('./b') console.log(bExports.add(10,30)) //b.js //挂载到这个 exports 对象中 export.add = function (x, y) { return x + y }
模块化 CommonJS
- Node 中,没有全局作用域,只有模块作用域(文件作用域)
- 通信规则:加载
require
、导出exports
require
加载只能执行其中代码,文件与文件之间由于是模块作用域,所以不会有污染的问题。 require
方法有两个作用:
- 加载文件模块并执行里面的代码
- 得到被加载文件中的
exports
导出的接口对象
let 自定义变量名 = require('模块')
在每个模块中,都提供了一个对象:exports
进行模块与模块的通信
- exports 默认是一个空对象
- 需要做的就是把所有需要被外部访问的成员挂载到这个
exports
接口对象中
// 导出多个成员(必须在对象中)
exports.a = 123
exports.b = function () {
console.log('bbb')
}
// 导出单个成员
module.exports = 'hello'
// 后者会覆盖前者
module.exports = function add(x, y) {
return x + y
}
在浏览器中也可以像在 Node 中的模块一样进行编程
<script>
标签来引用加载,而且你还必须考虑加载顺序问题require.js
第三方库 AMDsea.js
第三方库 CMD- Node 是在 8.5 版本之后对 ECMAScript6 module 进行了支持
模块原理
每个模块都有一个 module
对象,module
对象中有一个 exports
对象,我们可以把需要导出的成员都挂载到 module.exports
对象中。但书写起来麻烦,为了方便 Node 在每个模块都提供了一个成员 exports
,相当于 exports = module.exports
exports
和module.exports
的引用相同
console.log(exports === module.exports) //true
注意: 当一个模块需要导出单个成员时,必须使用 module.exports = xxx
,而不能使用 exports = xxx
- 一旦
exports = xxx
就与module.exports
无关了(引用改了),接下来exports.xxx
都没用了,就不会有任何输出 - 最终 return 的是
module.exports
// foo.js
module.exports = 'hello'
exports.foo = 'world'
// main.js
let foo = require('./foo')
console.log(foo) // 'hello'
// foo.js
module.exports = {
foo: 'bar'
}
exports = module.exports // 这里又重新建立两者的引用关系
exports.foo = 'hello'
// main.js
let foo = require('./foo')
console.log(foo) // { foo: 'bar' }
require 方法加载规则
深入浅出 Node.js(三):深入 Node.js 的模块机制
-
优先从缓存加载
避免重复加载,提高模块加载效率
// main.js
require('./a')
let fn = require('./b')
console.log(fn)
// a.js
console.log('a.js 被加载了')
let fn = require('./b')
console.log(fn)
// b.js
console.log('b.js 被加载了')
module.exports = function () {
console.log('this is b')
}
-
判断模块标识符
核心模块(只需按名字加载即可)
自己写的模块(路径形式的模块)
./
当前目录../
上一级目录/
当前文件模块所处根目录D:/xxx
绝对路径
第三方模块(node_modules)
-
第三方模块需要通过 npm 来下载(不可能有第三方模块和核心模块名字一致)
-
使用方式:
let xxx = require('[下载包]')
先去
node_modules
找到对应的下载包之后去
package.json
文件中找到对应的main
属性,main
属性记录了入口文件(一般都是对应index.js
->entry point
)以上都不满足则继续,则会依次进入上一级目录中的
node_modules
目中查找,直到当前磁盘根目录找不到,最后报错:Error: Cannot find module 'xxx'
Web 服务器开发 http
- 静态服务:主要提供静态资源,不同用户访问到的资源相同
- 动态服务:提供动态服务,不同用户访问到的资源不同
web 服务器:广义上来说,就是响应用户的需求,提供服务,当下所有的服务器软件都可以称之为 web 服务器软件
HTTP 服务器(静态服务):使用HTTP协议传输资源,提供服务
应用服务器(动态服务):一个特定应用的承载容器
url:统一资源定位符(一个 url 对应到一个资源)
ip 地址和端口号
-
ip地址用来定位计算机
-
端口号用来定位具体应用程序
-
一切需要联网通信的应用程序都会占用一个端口号
-
端口号范围 0~65536
-
可以同时开启多个服务,但一定要确保不同服务占用的端口号不一致才可以
Contenet-Type
-
在服务端默认发送的数据,其实是 utf8 编码的内容,浏览器在不知道服务器响应内容的编码的情况下会按照当前操作系统默认编码去解析,中文操作系统默认是 gbk,所以会出现乱码情况
服务器最好每次响应的数据是什么内容类型都告诉客户端
-
对于文本类型的数据,最好都加上编码,目的是为了防止中文解析乱码问题
-
图片不需要指定编码,常说的编码一般指的是:字符编码
let http = require('http')
let fs = require('fs')
let server = http.createServer()
let port = 5000
server.on('request', function (req, res) {
let url = req.url
// 服务器默认发送的数据,其实是utf8编码内容
if (url === '/plain') {
res.setHeader('Content-Type', 'text/plain; charset=utf-8')
res.end('hello 世界')
} else if (url === '/html') {
// 如果发送的是html格式的字符串,也需要告诉是text/html格式的内容
res.setHeader('Content-Type', 'text/html; charset=utf-8')
res.end('<p>hello html <a href="">click</a></p>')
} else if (url === '/a') {
fs.readFile('./resource/ab2.jpg', function (err, data) {
if (err) {
res.setHeader('Content-Type', 'text/plain; charset=utf-8')
res.end('文件读取失败,请稍后重试!')
} else {
// data默认是二进制数据,可以通过toString转为能识别的字符串
// res.end()支持两种数据类型,一种是二进制 一种是字符串
res.setHeader('Content-Type', 'image/jpeg')
res.end(data)
}
})
}
})
server.listen(port, () => console.log(`running at: http://localhost:${port}/`))
art-template
{{}}
语法 -> mustache
语法
模板引擎有很多:art-template
、ejs
、jade(pug)
、handlebars
、nunjucks
# 安装
npm i art-template
使用 render 方法,将模板源代码编译成函数并立刻执行
let http = require('http')
let fs = require('fs')
let template = require('art-template')
let server = http.createServer()
let port = 5000
let wwwDir = 'D:/Movie/www'
server.on('request', function (req, res) {
let url = req.url
fs.readFile('./template-apache.html', function (err, data) {
if (err) return res.end('404 Not Found.')
fs.readdir(wwwDir, function (err, files) {
if (err) return res.end('Can not find www dir.')
let htmlStr = template.render(data.toString(), {
title: '哈哈',
files: files,
})
res.end(htmlStr)
})
})
})
server.listen(port, () => console.log(`running at: http://localhost:${port}/`))
留言本案例
服务端渲染和客户端渲染
-
服务端渲染说白了就是在服务端使用模板引擎
-
客户端渲染不利于 SEO 搜索引擎优化
服务端渲染是可以被爬虫爬取到的,客户端异步渲染是很难被爬虫爬取到的
静态资源解析
-
浏览器收到 HTML 响应内容之后,就要开始从上到下依次解析
解析的过程中,如果发现:link、script、img、iframe、video、audio 等带有 src 或 href 属性标签的时候,浏览器会自动对这些请求发起新的请求
表单提交
- 表单控件元素必须有 name 属性
- 表单提交分为:默认提交行为、表单异步提交
- 对于表单提交的参数,可以使用 url 模块的 parse 方法进行解析
url.parse('url', true)
,直接将查询字符串转换为对象,该返回值的 pathname 属性是路径名(不包含 ? 之后的 query 内容)
通过服务器让客户端重定向
-
状态码设置为 302 临时重定向
statusCode
-
在响应头中通过 Location 设置跳转地址
setHeader
其它语言或库有对应 API,
res.redirect('url')
直接跳转
注意: 一次请求对应一次响应,响应结束 end
这次请求也就结束了
let http = require('http')
let fs = require('fs')
let template = require('art-template')
let url = require('url')
let server = http.createServer()
let port = 5000
let comments = new Array(5).fill(0).map((item, i) => {
item = {}
item.name = '张三' + i
item.message = '今天天气不错!'
item.dateTime = '2015-10-16'
return item
})
server.on('request', function (req, res) {
let parseObj = url.parse(req.url, true)
let pathname = parseObj.pathname // 不包含?之后的内容
if (pathname === '/') {
fs.readFile('./views/index.html', (err, data) => {
if (err) return res.end('404 Not Found.')
let ret = template.render(data.toString(), {
comments,
})
res.end(ret)
})
} else if (pathname === '/pinglun') {
let comment = parseObj.query
comment.dateTime = '2015-10-16'
comments.unshift(comment)
res.statusCode = 302
res.setHeader('Location', '/')
res.end()
} else if (pathname === '/post') {
fs.readFile('./views/post.html', (err, data) => {
if (err) return res.end('404 Not Found.')
res.end(data)
})
} else if (pathname.indexOf('/public/') === 0) {
fs.readFile('.' + pathname, (err, data) => {
if (err) return res.end('404 Not Found.')
res.end(data)
})
} else {
fs.readFile('./views/404.html', (err, data) => {
if (err) return res.end('404 Not Found.')
res.end(data)
})
}
})
server.listen(port, () => console.log(`running at: http://localhost:${port}/`))
Node 中的控制台
- Node.js 全局对象是 global
- 浏览器全局对象是 window
Cosole REPL(read、eval、print、loop)
- 这个环境的作用只是用来帮助我们做一些辅助测试,例如:可以在里面直接使用 Node 中的核心模块(不需要 require 加载)
npm
- node package manager(node 包管理器)
- npm 是一个命令行工具,只要安装了 node 就已经安装了 npm
- npm 也有版本概念,可以通过
npm --version
来查看 npm 版本,也可以用自己升级自己npm install --global npm
常用命令
-
npm init
(生成package.json
说明书)npm init -y
可以跳过向导,快速生成 -
npm install
一次性把 dependencies 选项中的依赖全部安装
简写:
npm i
-
npm install 包名
下载
简写:
npm i 包名
-
npm install --save 包名
下载并保存依赖项(
package.json
文件中的dependencies
选项)简写:
npm i -S 包名
-
npm uninstall 包名
删除
简写:
npm un 包名
-
npm uninstall --save 包名
删除并把依赖信息删除
简写:
npm un -S 包名
-
npm help
查看使用帮助
-
npm 命令 --help
查看指定命令帮助
-
版本安装
alpha 内测版、beta 公测版、rc 最终测试版、stable 正式稳定版
# 安装指定版本 npm i xxx@1.3.4 # 查看模块所有版本 npm view xxx versions # 即将发布版本 npm i xxx@next # 最后一个稳定版 npm i xxx@latest # 开发依赖 devDependencies npm i xxx --save-dev(-D) # 默认生成依赖 dependencies npm i xxx --save(-S)
-
查看安装在全局的目录
npm root -g
-
模块安装到全局
npm install xxx --global
简写:
npm i xxx -g
解决 npm 下载慢问题
npm taobao 以前是
https://npm.taobao.org/
,现在为:https://npmmirror.com/
(以前的还能用)
方案1:安装 cnpm
安装淘宝的 cnpm:
npm install -g cnpm --registry=https://registry.npmmirror.com
安装包的时候把以前的 npm
替换为 cnpm
# 走国外的npm服务器下载
npm install [name]
# 通过淘宝的服务器下载
cnpm install [name]
如果不想安装 cnpm
又想使用淘宝的服务器来下载,可以把这个选项加入到配置文件中:
# 设置源为淘宝源
npm config set registry https://registry.npmmirror.com
#查看npm配置信息
npm config list;
方案2:安装 nrm
通过淘宝镜像源安装 nrm
npm install -g nrm --registry=https://registry.npmmirror.com
切换源为淘宝源
# 切换为淘宝源
nrm use taobao
# 查看全部源
nrm ls
package.json
每一个项目都要有一个 package.json
文件(包描述信息,类似产品说明书)
- 这个文件可以通过
npm init
自动初始化出来 - 可以用
npm init -y
跳过如下配置,直接生成默认配置的package.json
D:\npmDemo>npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.
See `npm help json` for definitive documentation on these fields
and exactly what they do.
Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.
Press ^C at any time to quit.
package name: (package) npmDemo
version: (1.0.0)
description: 这是一个测试项目
entry point: (index.js) main.js
test command:
git repository:
keywords:
author: xxx
license: (ISC)
About to write to D:\npmDemo\package.json:
{
"name": "npmDemo",
"version": "1.0.0",
"description": "这是一个测试项目",
"main": "main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "xxx",
"license": "ISC"
}
Is this OK? (yes) yes
目前来说,最有用的是 dependencies
选项,这个帮助我们保存第三方包的依赖信息
- 如果
node_modules
删除了也不用担心,通过npm install
就会把package.json
中的dependencies
中所有的依赖项全部都下载回来
package-lock.json
npm 5
以前的版本是不会有 package-lock.json
这个文件的,npm 5
以后才加入了这个文件,当你安装包的时候,npm 都会生成或更新 package-lock.json
这个文件
-
npm 5
以前是需要通过--save
参数才能将其存到dependencies
中 -
npm 5
以后当你安装包的时候,会自动创建或更新package-lock.json
文件 -
package-lock.json
保存node_modules
中所有包的信息(版本、下载地址)这样的话重新
npm i
的时候速度回提升很多 -
名中有一个
lock
锁,是用来锁定版本的,防止自动升级新版如果项目中依赖了
jquery@1.11.1
,没有package-lock.json
文件如果重新
npm i
其实会下载最新版,而不是 1 版本中的最新版jquery@1.12.4
,而不是jquery@1.11.1
-
package.json
文件中^1.12.4
,1
是主版本号,12
是次版本号^
代表安装当前主版本号的最新版,比如1.11.1
可以安装到1.12.4
~
代表安装当前版本号的最新版,比如1.11.1
可以安装到1.11.3
,但不能安装到1.12.4
前面什么也不加,就是安装指定版本
# 更新升级npm
npm i npm -g
Express
原生 http 模块在某些方面表现不足以应对我们的开发需求,所以我们使用框架来加快我们的开发效率,框架的目的就是提高效率,让代码风格高度统一
安装:
npm install express
起步:
const express = require('express')
// 创建服务器应用程序,相当于http.createServer()
const app = express()
const port = 3003
// 公开指定目录,这样就可以通过/public/xxx访问public目录下的资源
app.use('/public/', express.static('./public/'))
// 当服务器收到get请求根目录的时候,执行回调函数
app.get('/', (req, res) => {
res.send('express home')
})
app.listen(port, () => console.log(`running at: http://localhost:${port}/`))
后台运行第三方工具
- 修改代码自动重启
可以使用第三方命令行工具 nodemon
来帮我们解决修改代码重启服务器问题
npm install --global nodemon
只要是通过 nodemon
启动的服务,它会监视文件变化,当文件变化时,自动帮你重启服务器
nodemon app.js
- 后台运行
pm2 是一个进程管理工具,可以用它来管理你的 node 进程,并查看 node 进程的状态
npm install --global pm2
启动进程并其别名,方便查看或停止
# 起别名
pm2 start server.js --name mi
其他用法可以查看 使用PM2来部署nodejs项目
# 重启(需要有别名)
pm2 restart mi
# 停止
pm2 stop mi
# 查看状态
pm2 list
路由和静态服务 API
- 当你以 get 方法请求的时候,执行对应的处理函数
app.get('/', (req, res) => {
console.log(req.query)
res.send('hello get')
})
- 当你以 post 方法请求的时候,执行对应的处理函数
app.post('/', (req, res) => {
res.send('hello post')
})
- 当以
/public/
开头的时候,去./public
目录去找对应文件
app.use('/public/', express.static('./public/'))
- 第一个参数可以省略,会直接去
./public
目录去找对应文件
app.use(express.static('./public/'))
使用 art-template
模板引擎
安装:
npm i art-template express-art-template
配置:
- 当渲染以
.html
结尾的文件的时候,使用express-art-template
模板引擎 - 注意: 虽然
art-template
不用在外面引入,但是express-art-template
依赖了art-template
,所以art-template
也是必须安装的
app.engine('html', require('express-art-template'))
render 的使用:
-
Express 为 Response 响应对象提供了一个方法:render
render 方法模式是不可使用,但是如果配置了模板引擎就可以使用了
-
res.render('html模板名', { 模板数据 })
第一个参数不能写路径,默认会去项目中的 views 目录查找该模板文件(开发人员把所有视图文件放到 views 目录中)
如果希望修改默认的 views
视图渲染存储目录,可以使用 app.set
app.set('views', '目录路径')
模板引擎高级用法
把页面中公共的部分放到模板页中,使用 extend
继承页面公共部分,使用 include
引入页面公共部分,使用 block
填充可改区域(相当于插槽)
layout.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>layout.html</title>
<link rel="stylesheet" href="/node_modules/bootstrap/dist/css/bootstrap.css">
{{ block 'head' }}{{ /block }}
</head>
<body>
{{ include './header.html' }}
{{ block 'content' }}
<h1>默认内容</h1>
{{ /block }}
{{ include './footer.html' }}
<script src="/node_modules/jquery/dist/jquery.js"></script>
<script src="/node_modules/bootstrap/dist/js/bootstrap.js"></script>
{{ block 'script' }}{{ /block }}
</body>
</html>
header.html
<div>
<h1>公共的头部</h1>
</div>
footer.html
<div>
<h1>公共的底部</h1>
</div>
index.html
{{ extend './layout.html' }}
{{ block 'head' }}
<style>
body {
background-color: skyblue;
}
</style>
{{ /block }}
{{ block 'content' }}
<div>
<h1>index 页面填坑内容</h1>
</div>
{{ /block }}
{{ block 'script' }}
<script>
window.alert('index 页面自己的 js 脚本')
</script>
{{ /block }}
获取请求体数据 body-parser
- 在 Express 中内置了 API,可以直接通过
req.query
来获取 GET 请求数据 - 在 Express 中没有内置获取表单 POST 请求的 API,需要使用一个第三方库
body-parser
来通过req.body
来获取数据
安装:
npm i body-parser
使用:
bodyParser.xxx
已经弃用,需要把 bodyParser 改为 express
const express = require('express')
const bodyParser = require('body-parser')
const app = express()
const port = 3003
let comments = new Array(5).fill(0).map((item, i) => {
item = {}
item.name = '张三' + i
item.message = '今天天气不错!'
item.dateTime = '2015-10-16'
return item
})
app.use('/public/', express.static('./public/'))
// 配置使用 art-template 模板引擎
app.engine('html', require('express-art-template'))
// bodyParser获取请求数据
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())
app.get('/', (req, res) => {
res.render('index.html', {
comments,
})
})
app.get('/post', (req, res) => {
res.render('post.html')
})
app.get('/formAction', (req, res) => {
let comment = req.query
comment.dateTime = '2015-10-16'
comments.unshift(comment)
res.redirect('/')
})
app.post('/formAction', (req, res) => {
let comment = req.body
comment.dateTime = '2015-10-16'
comments.unshift(comment)
res.redirect('/')
})
app.listen(port, () => console.log(`running at: http://localhost:${port}/`))
CRUD
Create(创建)、Read(读取)、Update(更新)、Delete(删除)
路由设计
路由:应用如何响应请求的一种规则
请求方法 | 请求路径 | get参数 | post参数 | 备注 |
---|---|---|---|---|
GET | /students | 渲染首页 | ||
GET | /students/new | 渲染添加学生页面 | ||
POST | /students/new | name,age,gender,hobbies | 处理添加学生请求 | |
GET | /students/edit | id | 渲染编辑页面 | |
POST | /students/edit | id,name,age,gender,hobbies | 处理编辑请求 | |
GET | /students/delete | id | 处理删除请求 |
提取路由模块
方法1:
- 在
router.js
导出一个函数,这个函数在app.js
执行
/* app.js */
const router = require('./router')
const app = express()
router(router)
/* router.js */
module.exports = function (app) {
app.get('/', (req, res) => {})
}
方法2:
- Express 提供了一种更好的方式
/* app.js */
const router = require('./router')
const app = express()
// 把路由容器挂载到app服务中
app.use(router)
/* router.js */
const express = require('express')
// 1.创建一个路由容器
let router = express.Router()
// 2.把路由都挂载到router路由容器中
router.app.get('/', (req, res) => {})
// 3.把router导出
modules.exports = router
模块职责要清晰且单一,不要混用。划分模块的目的:增强代码的可维护性,提高开发效率
app.js
-
创建服务器
-
做一些服务相关配置
模板引擎、
body-parser
解析 POST 请求体、提供静态资源服务 -
挂载路由
-
监听端口并启动服务
router.js
- 处理路由
- 根据不同的请求方法 + 请求路径设置具体的请求函数
提取操作文件模块
student.js
-
数据操作文件模块
操作文件中的数据,只操作文件,不关心业务
如下版本为回调函数版本:
/* router.js */
const fs = require('fs')
const Student = require('./student')
router.get('/students', (req, res) => {
Student.find((err, students) => {
if (err) return res.status(500).send('Server Error')
res.render('index.html', {
fruits: ['苹果', '香蕉', '橘子'],
students: students,
})
})
})
/* student.js */
const fs = require('fs')
const dbPath = './db.json'
exports.find = function (callback) {
// 第二个参数可选,会按照对应编码格式进行编码(或者使用data.toString)
fs.readFile(dbPath, 'utf8', (err, data) => {
if (err) return callback(err)
// 从文件读取到的数据是字符串,需要转换成对象
callback(null, JSON.parse(data).students)
})
}
如下版本为 Promise 版本:
/* router.js */
const fs = require('fs')
const Student = require('./student')
router.get('/students', (req, res) => {
Student.find()
.then(students => {
res.render('index.html', {
fruits: ['苹果', '香蕉', '橘子'],
students,
})
})
.catch(() => {
res.status(500).send('Server Error')
})
})
/* student.js */
const fs = require('fs')
const dbPath = './db.json'
exports.find = function () {
return new Promise((resolve, reject) => {
// 第二个参数可选,会按照对应编码格式进行编码(或者使用data.toString)
fs.readFile(dbPath, 'utf8', (err, data) => {
if (err) reject(err)
// 从文件读取到的数据是字符串,需要转换成对象
resolve(JSON.parse(data).students)
})
})
}
app.js
注意: 配置模板引擎和 body-parser
一定要在 app.use(router)
挂载路由之前
const express = require('express')
const router = require('./router')
const app = express()
const port = 3003
app.use('/node_modules/', express.static('./node_modules/'))
app.use('/public/', express.static('./public/'))
app.engine('html', require('express-art-template'))
app.use(express.urlencoded({ extended: false }))
app.use(express.json())
app.use(router)
app.listen(port, () => console.log(`running at: http://localhost:${port}/`))
router.js
- POST 请求
req.body
- GET 请求
req.query
const fs = require('fs')
const express = require('express')
let router = express.Router()
let Student = require('./student')
router.get('/students', (req, res) => {
Student.find()
.then(students => {
res.render('index.html', {
fruits: ['苹果', '香蕉', '橘子'],
students,
})
})
.catch(() => {
res.status(500).send('Server Error')
})
})
router.get('/students/new', (req, res) => {
res.render('new.html')
})
router.post('/students/new', (req, res) => {
const std = req.body
Student.save(std)
.then(() => {
res.redirect('/students')
})
.catch(() => {
res.status(500).send('Server Error')
})
})
router.get('/students/edit', (req, res) => {
const id = parseInt(req.query.id)
Student.findById(id)
.then(student => {
res.render('edit.html', {
student,
})
})
.catch(() => {
res.status(500).send('Server Error')
})
})
router.post('/students/edit', (req, res) => {
const std = req.body
Student.updateById(std)
.then(() => {
res.redirect('/students')
})
.catch(() => {
res.status(500).send('Server Error')
})
})
router.get('/students/delete', (req, res) => {
const id = parseInt(req.query.id)
Student.deleteById(id)
.then(() => {
res.redirect('/students')
})
.catch(() => {
res.status(500).send('Server Error')
})
})
module.exports = router
db.json
const fs = require('fs')
const dbPath = './db.json'
function rfPromise() {
return new Promise((resolve, reject) => {
fs.readFile(dbPath, 'utf8', (err, data) => {
if (err) reject(err)
resolve(JSON.parse(data).students)
})
})
}
// 获取学生列表
exports.find = () => rfPromise()
// 根据id获取学生信息对象
exports.findById = id => {
return rfPromise().then(students => {
return students.find(item => item.id === id)
})
}
// 添加保存学生
exports.save = std => {
return rfPromise().then(students => {
std.id = parseInt(Math.random().toString(10).slice(2, 5))
students.push(std)
const ret = JSON.stringify({
students,
})
fs.writeFile(dbPath, ret, err => {
if (err) Promise.reject(err)
})
})
}
// 更新学生
exports.updateById = std => {
return rfPromise().then(students => {
std.id = parseInt(std.id)
students.find((item, i) => {
if (item.id === std.id) Object.assign(students[i], std)
})
const ret = JSON.stringify({
students,
})
fs.writeFile(dbPath, ret, err => {
if (err) Promise.reject(err)
})
})
}
// 删除学生
exports.deleteById = id => {
return rfPromise().then(students => {
students = students.filter(item => item.id !== id)
const ret = JSON.stringify({
students,
})
fs.writeFile(dbPath, ret, err => {
if (err) Promise.reject(err)
})
})
}
数据库
MongoDB
MongoDB 基础学习可以看我这篇文章:MongoDB 学习
注意:此 Mongoose 使用的 5 版本的最新版
student.js
/* npm i mongoose@5 */
const mongoose = require('mongoose')
mongoose.connect('mongodb://127.0.0.1/student_doc', {
useNewUrlParser: true,
useUnifiedTopology: true,
auth: { authSource: 'admin' },
user: 'root',
pass: 'root'
})
mongoose.connection.once('open', () => console.log('数据库连接成功'))
const Schema = mongoose.Schema
const studentSchema = new Schema({
name: {
type: String,
required: true,
},
gender: {
type: Number,
enum: [0, 1],
default: 0,
},
age: Number,
hobbies: String,
})
const StudentSchema = mongoose.model('students', studentSchema)
module.exports = StudentSchema
-
router.js
save 方法是 Document 对象的方法,需要
new Model
const fs = require('fs')
const express = require('express')
let router = express.Router()
let Student = require('/student')
router.get('/students', (req, res) => {
Student.find()
.then(students => {
res.render('index.html', {
fruits: ['苹果', '香蕉', '橘子'],
students,
})
})
.catch(() => {
res.status(500).send('Server Error')
})
})
router.get('/students/new', (req, res) => {
res.render('new.html')
})
router.post('/students/new', (req, res) => {
const std = req.body
new Student(std).save()
.then(() => {
res.redirect('/students')
})
.catch(() => {
res.status(500).send('Server Error')
})
})
router.get('/students/edit', (req, res) => {
const id = req.query.id.replace(/\"/g, '')
Student.findById(id)
.then(student => {
res.render('edit.html', {
student,
})
})
.catch(() => {
res.status(500).send('Server Error')
})
})
router.post('/students/edit', (req, res) => {
const std = req.body
const id = std.id
Student.findOneAndUpdate(id, std)
.then(() => {
res.redirect('/students')
})
.catch(() => {
res.status(500).send('Server Error')
})
})
router.get('/students/delete', (req, res) => {
const id = req.query.id.replace(/\"/g, '')
Student.findByIdAndDelete(id)
.then(() => {
res.redirect('/students')
})
.catch(() => {
res.status(500).send('Server Error')
})
})
module.exports = router
MySQL
MySQL 基础学习可以看我这篇文章:MySQL 学习
/* npm i mysql */
const mysql = require('mysql')
// 1.创建连接
const connection = mysql.createConnection({
host: '127.0.0.1',
user: 'root',
password: 'root',
database: 'users'
})
// 2.连接数据库
connection.connect()
// 3. 执行数据操作
connection.query('INSERT INTO users VALUES(NULL, "admin", "123456")', function (error, results) {
if (error) throw error;
console.log('The solution is: ', results);
});
// 4. 关闭连接
connection.end();
path 模块
path 模块
path.basename(path, [ext])
返回 path
中的最后一部分
> path.basename('c:/a/b/index.js')
'index.js'
> path.basename('c:/a/b/index.js', '.js')
'index'
path.dirname(path)
返回 path
的目录名
path.extname(path)
返回 path
的扩展名
path.isAbsolute(path)
确定 path
是否为决定路径
> path.dirname('c:/a/b/index.js')
'c:/a/b'
> path.extname('c:/a/b/index.js')
'.js'
> path.isAbsolute('c:/a/b/index.js')
true
path.parse(path)
返回一个对象,属性为 path
的重要元素
> path.parse('c:/a/b/index.js')
{
root: 'c:/',
dir: 'c:/a/b',
base: 'index.js',
ext: '.js',
name: 'index'
}
path.join([...paths])
将给定的 path
连接在一起
> path.join('c:/a/b', 'index.js')
'c:\\a\\b\\index.js'
> path.join('c:/a/b', 'c', 'index.js')
'c:\\a\\b\\c\\index.js'
Node 中的其他成员
在每个模块中,除了 require
、exports
等模块相关 API 之外,还有两个特殊成员:
__dirname
获取当前文件所属目录的绝对路径__filename
获取当前文件的绝对目录__dirname
和__filename
不受执行 Node 命令的所属路径影响
在文件操作中,使用相对路径是不可靠的,因为 Node 中文件操作的路径是相对于执行 Node 命令所处的路径,不是相对于文件的路径
- 需要把相对路径变为绝对路径(绝对路径不受任何影响)
- 在拼接路径的过程中,为了避免手动拼接出现错误,推荐使用
path.join()
方法结合__dirname
来拼接
fs.readFile(path.join(__dirname, '/a.txt'), 'utf8', (err, data) => {
if (err) throw err
console.log(data)
})
注意: 模块中的路径标识和路径没关系,不受影响(就是相对于文件模块)
Blog 案例
目录结构
|- controllers //
|- models // 数据库
|- node_modules // 第三方库
|- public // 公共静态资源
|- routes // 路由
|- views // 页面
|- _layouts
|- _partials
|- settings
|- topic
|- index.html
|- login.html
|- register.html
|- app.js // 服务器
|- package.json // 第三方库描述文件
|- package-lock.json // 第三方库版本锁定文件(npm5以后才有)
路由设计
路由 | 方法 | get 参数 | post 参数 | 是否需要登录 | 备注 |
---|---|---|---|---|---|
/ | GET | 渲染首页 | |||
/register | GET | 渲染注册页面 | |||
/register | POST | email、nickname、password | 处理注册请求 | ||
/login | GET | 渲染登陆界面 | |||
/login | POST | email、password | 处理登录请求 | ||
/loginout | GET | 处理退出请求 |
同步异步
表单提交问题:
-
form
表单具有默认提交行为,默认是同步的。同步表单提交,浏览器会等待服务器响应结果可能出现锁死(转圈)情况 -
表单同步提交之后,无论服务器响应的是什么,都会直接把响应的结果覆盖掉当前页面
不推荐:可以重新渲染这个页面,并使用
input
中的value
属性把对应值重新渲染上去推荐:使用
ajax
异步提交,可以解决这个问题
重定向问题:
-
服务端重定向只针对同步请求才有效,异步请求无效
即使显示
Status Code: 302 Found
和Location: /
也是无法跳转的 -
需要客户端自己处理,使用
window.location.href = '/'
进行跳转
router.post('/register', async (req, res) => {
new User(body).save(() => {
res.redirect('/')
})
})
express-session
npm i express-session
配置:
const session = require('express-session')
app.use(
session({
// 配置加密字符串,它会在原有加密基础之上和这个字符串拼接起来加密
secret: 'encode@3#!8^k.j$',
resave: false,
// 无论是否使用Session,都默认分配一把钥匙
saveUninitialized: true,
})
)
使用:
// 添加Session数据
req.session.foo = 'bar'
// 获取Session数据
req.session.foo
注意:默认 Session 数据是内存存储的,服务器一旦重启就会丢失,真正的生产环境会把 Session 是持久化存储的
代码
app.js
const express = require('express')
const path = require('path')
const router = require('./router.js')
const session = require('express-session')
const app = express()
const port = 3003
app.use('/node_modules/', express.static('./node_modules/'))
app.use('/public/', express.static('./public/'))
app.engine('html', require('express-art-template'))
app.set('views', path.join(__dirname, './views'))
app.get('/', (req, res) => {
res.render('index.html')
})
app.use(express.urlencoded({ extended: false }))
app.use(express.json())
app.use(
session({
// 配置加密字符串,它会在原有加密基础之上和这个字符串拼接起来加密
secret: 'encode@3#!8^k.j$',
resave: false,
// 无论是否使用Session,都默认分配一把钥匙
saveUninitialized: false
})
)
app.use(router)
app.use((req, res) => {
res.render('404.html')
})
app.listen(port, () => console.log(`running at: http://localhost:${port}/`))
router.js
const express = require('express')
const User = require('./models/user')
const md5 = require('blueimp-md5')
const router = express.Router()
function serverError(res) {
return res.status(500).json({
err_code: 500,
message: 'server error',
})
}
router.get('/', (req, res) => {
res.render('index.html', {
user: req.session.user,
})
})
router.get('/login', (req, res) => {
res.render('login.html')
})
router.post('/login', async (req, res) => {
const body = req.body
try {
const user = await User.findOne({ email: body.email, password: md5(md5(body.password)) })
if (!user) {
return res.status(200).json({
err_code: 1,
message: 'email or password is invalid',
})
}
req.session.user = user
return res.status(200).json({
err_code: 0,
message: 'OK',
})
} catch (err) {
serverError(res)
}
})
router.get('/register', (req, res) => {
res.render('register.html')
})
router.post('/register', async (req, res) => {
const body = req.body
try {
if (await User.findOne({ email: body.email })) {
return res.status(200).json({
err_code: 1,
message: 'email is already exists',
})
}
if (await User.findOne({ nickname: body.nickname })) {
return res.status(200).json({
err_code: 2,
message: 'nickname is already exists',
})
}
// 邮箱和昵称不存在添加到数据库
body.password = md5(md5(body.password))
await new User(body).save()
// 注册成功,使用Session记录用户的登录状态
req.session.user = user
// json方法会自动帮你把对象转换为字符串发给浏览器
return res.status(200).json({
err_code: 0,
message: 'OK',
})
} catch (err) {
serverError(res)
}
})
router.get('/logout', (req, res) => {
req.session.user = null
res.redirect('/login')
})
module.exports = router
models/user.js
const mongoose = require('mongoose')
mongoose.connect('mongodb://127.0.0.1/blog', {
useNewUrlParser: true,
useUnifiedTopology: true,
auth: { authSource: 'admin' },
user: 'root',
pass: 'root',
})
const Schema = mongoose.Schema
const userSchema = new Schema({
email: {
type: String,
require: true,
},
nickname: {
type: String,
require: true,
},
password: {
type: String,
require: true,
},
create_time: {
type: Date,
default: Date.now, // 注意:不要写Date.now(),因为会即刻调用
},
last_modified_time: {
type: Date,
default: Date.now,
},
avatar: {
type: String,
default: '/public/img/avatar-default.png',
},
bio: {
type: String,
default: '',
},
gender: {
type: Number,
enum: [-1, 0, 1],
default: -1,
},
birthday: {
type: Date,
},
status: {
type: Number,
// 0 没有权限限制 1 不可以评论 2 不可以登录
enum: [0, 1, 2],
default: 0,
},
})
module.exports = mongoose.model('User', userSchema)
中间件
参考文档:using-middleware
中间件:把很复杂的事情分割成单个,然后依次有条理的执行。目的:提高代码灵活性、动态可扩展性
-
通俗来讲,中间件就是一个方法
把数据请求响应分步骤来处理,每一个步骤都是一个中间件处理环节
const http = require('http')
const url = require('url')
const cookie = require('./middleWares/cookie')
const postBody = require('./middleWares/post-body')
const query = require('./middleWares/query')
const server = http.createServer((req, res) => {
// 解析请求地址的get参数
// const urlObj = url.parse(req.url, true)
// req.query = urlObj.query
query(req)
// 解析请地址的post参数
// req.body = {}
postBody(req)
// 解析cookie
// req.cookie = {}
cookie(req)
})
const port = 3005
server.listen(port, () => console.log(`running at: http://localhost:${port}/`))
应用程序级别的中间件
中间件的本质就是一个请求处理方法,该方法接收三个参数:
-
request 请求对象
-
response 响应对象
-
next 下一个中间件
如果请求进入中间件之后,没有调用 next 则代码会停在中间件(类似迭代器)
如果调用了 next 则继续向后查找第一个匹配的中间件
万能匹配(不关心任何请求路径和请求方法的中间件):
// 中间件:处理请求的,本质就是个函数
app.use((req, res, next) => {
console.log('1')
next()
})
关心请求路径和请求方法的中间件:
// 以 /a(/abc、/a/b) 开头的路径中间件
app.use('/a', (req, res, next) => {
console.log('a')
next()
})
路由级别中间件
// 如果没有能匹配的中间件,则 Express 会默认输出:Cannot GET 路径
app.get('/a', (req, res) => {
res.send('get')
})
app.post('/a', (req, res) => {
res.send('post')
})
app.put('/a', (req, res) => {
res.send('put')
})
app.delete('/a', (req, res) => {
res.send('delete')
})
错误处理中间件
配置使用 404 中间件:
app.use((req, res) => {
res.render('404.html')
})
配置错误处理中间件:
app.use((err, req, res, next) => {
res.status(500).send(err.message)
})
错误处理中间件需要注意:
- 参数一个都不能少,否则会视为普通的中间件
err, req, res, next
- 错误处理中间件需要在请求之后引用
当调用 next()
传参后,则直接进入到全局错误处理中间件方法中
app.get('/a', (req, res, next) => {
fs.readFile('.a/bc', funtion() {
if (err) {
// 当发生全局错误的时候,可以调用next传递错误对象
next(err)
}
})
})
内置中间件
参考文档:static
express.static(提供静态文件)
第三方中间件
参考文档:middleware
- body-parser
- compression
- cookie-parser
- mogran
- response-time
- server-static
- session
问题
Please verify that the package.json has a valid “main” entry
- 删除 node_modules 和
package.json
重新安装即可
Error: Cannot find module 'E:\express-demo\node_modules\debug\src\index.js'. Please verify that the package.json has a valid "main" entry
更多推荐
所有评论(0)