fs模块
读取文件 readFile()
// 1. 导入 fs 模块,来操作文件
const fs = require('fs')
// 2. 调用 fs.readFile() 方法读取文件
// 参数1:读取文件的存放路径
// 参数2:读取文件时候采用的编码格式,一般默认指定 utf8
// 参数3:回调函数,拿到读取失败和成功的结果 err dataStr
fs.readFile('./files/11.txt', 'utf8', function(err, dataStr) {
// 2.1 打印失败的结果
// 如果读取成功,则 err 的值为 null
// 如果读取失败,则 err 的值为 错误对象,dataStr 的值为 undefined
if (err) {
return console.log('读取文件失败!' + err.message)
}
console.log('读取文件成功!' + dataStr)
})
写入文件 writeFile()
字符集 utf8
不写utf8(字符串),默认是文件流 buffer(二进制)
// 1. 导入 fs 文件系统模块
const fs = require('fs')
// 2. 调用 fs.writeFile() 方法,写入文件的内容
// 参数1:表示文件的存放路径
// 参数2:表示要写入的内容
// 参数3:回调函数
fs.writeFile('./files/3.txt', 'ok123', function(err) {
// 2.1 如果文件写入成功,则 err 的值等于 null
// 2.2 如果文件写入失败,则 err 的值等于一个 错误对象
// console.log(err)
if (err) {
return console.log('文件写入失败!' + err.message)
}
console.log('文件写入成功!')
})
__dirname
表示当前文件所处的目录
path 路径模块
path.join()
拼接文件 路径,识别相对路径符号 ../, ./
const path = require('path');
fs.readFile(path.join(__dirname, './files/1.txt'), 'utf8', function(err, dataStr) {
if (err) {
return console.log(err.message)
}
console.log(dataStr)
})
path.basename()
从文件路径中,拿到文件名称-- index.html | index
const path = require('path')
// 定义文件的存放路径
const fpath = '/a/b/c/index.html';
const fullName = path.basename(fpath);// index.html
const nameWithoutExt = path.basename(fpath, '.html');// index
path.extname()
获取 路径 中的扩展名部分-- .html
const path = require('path')
// 这是文件的存放路径
const fpath = '/a/b/c/index.html';
const fext = path.extname(fpath);// .html
http 模块
node提供的,用来创建 web服务器的模块
IP地址
IP是在网络中 找对应的电脑
互联网中 每台web服务器,都有自己 唯一的IP地址
// 终端 查看百度的ip地址
ping www.baidu.com
39.156.66.18
域名和域名服务器
域名是为了 方便记忆 IP
IP地址和域名一一对应( 39.156.66.18----www.baidu.com )
端口
端口是在网络中 找电脑里面的 软件
创建web服务器
- 01.js
// 1. 导入 http 模块
const http = require('http')
// 2. 创建 web 服务器实例
const server = http.createServer()
// 3. 为服务器实例绑定 request 事件,监听客户端的请求
// req-require/request请求,res-response响应
server.on('request', (req, res) => {
// 定义一个字符串,包含中文的内容
const str = `您请求的 URL 地址是 ${req.url},请求的 method 类型为 ${req.method}`
// 调用 res.setHeader() 方法,设置 Content-Type 响应头,解决中文乱码的问题
res.setHeader('Content-Type', 'text/html; charset=utf-8')
// res.end() 将内容响应给客户端
res.end(str)
})
// 4. 启动服务器
server.listen(80, () => {
console.log('server running at http://127.0.0.1')
})
- 终端输入
// 启动服务器
node 01.js
// 终止服务器
control+c
模块化
模拟require的实现
- myRequire.js
const fs = require('fs');
function myRequire(filePath) {
let myDirname = '读取文件中。。。。\n';
fs.readFile(filePath, 'utf8', function(err, content) {
if (err) return console.log(err.message);
// 读取成功,就执行读取的代码
eval(content);
})
}
myRequire('./source.js')
- source.js
console.log(myDirname);
console.log('被读取的文件是我');
模块作用域
-
概念: 模块内的变量,外界不可访问
-
目的: 防止全局变量污染
module对象
存储了和当前模块相关的属性
module.exports对象
将模块内的成员共享出去,供外界使用
注意:使用require导入的结果,永远以module.exports 指向的对象为准
// 在一个自定义模块中,默认情况下, module.exports = {}
const age = 20
// 导出方法1: module.exports 【点语法】
module.exports.username = 'zs'
// 向 module.exports 对象上挂载 sayHello 方法
module.exports.sayHello = function() {
console.log('Hello!')
}
module.exports.age = age
// 导出方法2(推荐): 让 module.exports 【指向一个全新的对象】
// 会覆盖前面 单独挂载的 属性,导出的为该对象
module.exports = {
nickname: '小黑',
sayHi() {
console.log('Hi!')
}
}
// 导出方法3(不推荐): exports 【点语法】
exports.dogname= 'tomato';
模拟module.exports实现原理
- 模拟实现module.exports.js
const fs = require('fs');
function myRequire(filePath) {
let myDirname = '读取文件中。。。。\n';
// 1.创建module局部变量,并设置 初始值和属性
let module = {
// ...
exports: {} // 用来导出数据和方法的属于
// ...
}
// 1.2创建 exports局部变量,用来 方便 访问module.exports
let exports=module.exports;
// 2.根据传入的路径,同步读取 文件中的代码字符串
let content = fs.readFileSync(filePath, 'utf8');
// 3.读取成功,就执行读取的代码
eval(content);
// 4.返回 需要导出的数据和方法
return module.exports;
}
// 测试 导入js模块 .js后缀名可以省略
let m = myRequire('./source.js');
console.log(m); // {name:'33'}
- source.js
module.exports.name = '33';
模块化规范
CommonJS规定了 模块的特性和各模块之间如何相互依赖
npm与包
包(第三方模块)
包-参考文档 Node Package Manager(npm包管理攻击)
下载包
- 下载 moment包
//初始化 文件,生成 package-lock.json 文件
npm init -y
// 安装 moment 包
npm i monent// i--install
// 安装指定版本,会自动覆盖已安装的版本
npm i monent@2.29.1
- 导入包
// 1.导入 下载好的 moment 包(moment 导入 返回的是一个 函数)
const moment = require('moment')
// 2.使用 moment 函数
// 2.1 获取 当前 日期时间 --------------------------------
let nowTime = moment()
console.log(nowTime) // -> Moment<2021-07-16T18:40:23+08:00>
// 2.2 获取 当前 日期时间,并格式化-------------------------
let nowTime2 = moment().format('YYYY-MM-DD HH:mm:ss')
console.log(nowTime2) // '2021-07-16 18:41:43'
// 2.3 获取 指定日期时间,并格式化--------------------------
// a.传入 日期 对象
let dateNow = new Date()
let nowTime3 = moment(dateNow).format('YYYY-MM-DD HH:mm:ss')
console.log(nowTime3) // '2021-07-16 18:41:43'
// b.传入 日期 字符串
let nowTime4 = moment('2021-07-16T10:45:05.942Z').format('YYYY-MM-DD HH:mm:ss')
console.log(nowTime4) // '2021-07-16 18:45:05'
// c.传入 总毫秒数 字符串
let nowTime4 = moment(1626432356541).format('YYYY-MM-DD HH:mm:ss')
console.log(nowTime4) // '2021-07-16 18:45:05'
初始化包
//初始化 文件,生成 package-lock.json 文件
npm init -y
一次性安装所有依赖包
npm i;// install
卸载包
npm uninstall moment
解决下包速度慢的问题 nrm
将国外的官方服务器 切换为 国内的淘宝服务器
# 查看当前的 下包镜像源
npm config get registry
# 将下包的镜像源 切换为 淘宝镜像源
npm config set registry=http://registry.npm.taobao.org/
# 检查镜像源是否下载成功
npm config get registry
nrm工具(更方便的切换包)
# 将nrm安装为全局 可用的 工具
npm i nrm -g
# 查看 所有可用的镜像源
nrm ls
# 将下包的镜像源 切换为 taobao镜像
nrm use taobao
包的分类
项目包
被安装到 项目的
node_modules目录中的包
-
开发依赖包(被记录到
devDependencies节点中的包,只在开发期间会用到) -
核心依赖包(被记录到
dependencies节点中的包,在开发期间和项目上线之后都会用到)
package.json 与 package-lock.json
package.json只记录项目直接依赖的包package-lock.json记录项目直接与间接依赖的所有包(且保存了包的hash值)
全局包
执行npm install 命令时,提供了-g参数,就会将包安装为 全局包
-
只有工具性质的包,才有全局安装的必要性。因为它们提供了好用的终端命令
-
判断某个包是否需要全局安装后才能使用,可以参考官方提供的使用说明即可
-
不会记录到 项目的 package.json文件中
# 全局 安装指定的包
npm install 包名 -g
# 全局 卸载指定的包
npm uninstall包名 -g
# 全局安装的文件 的默认目录
/usr/local/lib/node_modules
i5ting_toc 将md转换为html
# 安装为全局包
npm install -g i5ting_toc
# 调用i5ting_toc,将md文件 转 html
i5ting_toc -f 要转换的md文件路径 -o
规范的包结构
-
包必须以单独的目录而存在
-
包的顶级目录下要必须包含 package.json 这个包管理配置文件
-
package.json 中必须包含
name,version,main这三个属性,分别代表包的名字、版本号、包的入口。
开发自己的包
初始化包的基本结构
- 新建 itheima-tools 文件夹,作为包的根目录
- 在 itheima-tools 文件夹中,新建如下三个文件:
- package.json (包管理配置文件)
- 注意:name不能有大写
- index.js (包的入口文件)
- README.md (包的说明文档)
- package.json (包管理配置文件)
完善包的文件代码
/package.json/
{
"name": "jean-sayhi",// 注意不能有大写
"version": "1.0.0",
"description": "测试jean的打招呼包",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": ["sayHi", "jean", "test"],
"author": "",
"license": "ISC"
}
/index.js/
// 导入成员
const sayHi = require('./src/sayhi.js');
// 导出成员
module.exports = {
// 对象中的扩展运算符(...)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中
...sayHi
}
/src/sayhi.js/
// 打招呼
function sayHi(username) {
console.log(`${username},hello~Welcome to use Jean's package!`);
}
module.exports = {
sayHi
}
/README.md/
## 安装
npm i Jean-sayHi
## 导入
const Jeans = require('Jean-sayHi');
## 打招呼
// 调用 dateFormat 对时间进行格式化
const str = Jean.sayHi('super');
// 结果 2020-04-03 17:20:58
console.log(str)
发布包
注意:在终端执行命令前,必须 先把下包的服务器地址切换为 npm 的官方 服务器。否则会导致发布包失败!
1. 登录npm
npm login
2. 将终端切换到`包的根目录`,发布包
npm publish
删除已发布的包
npm unpublish 包名 --force
模块的加载机制
优先从缓存中加载
模块在第一次加载后
会被缓存。 这也意味着多次调用 require() 不会导致模块的代码被执行多次。
注意:不论是内置模块、用户自定义模块、还是第三方模块,它们都会优先从缓存中加载,从而提高模块的加载效率。
内置模块的加载机制
内置模块是由 Node.js 官方提供的模块,内置模块的加载优先级最高。
自定义模块的加载机制
文件
必须指定以
./ 或 ../开头的路径标识符。在加载自定义模块时,否则会当作内置模块或第三方模块进行加载。
在使用 require() 导入自定义模块时,如果省略了文件的扩展名,则 Node.js 会按顺序分别尝试加载以下的文件:
- 按照确切的文件名进行加载
- 补全 .js 扩展名进行加载
- 补全 .json 扩展名进行加载
- 补全 .node 扩展名进行加载
- 加载失败,终端报错
文件夹(目录作为模块)
当把目录作为模块标识符,传递给 require() 进行加载的时候,有三种加载方式:
-
在被加载的目录下查找一个叫做
package.json的文件,并寻找main属性,作为 require() 加载的入口 -
如果目录里没有 package.json 文件,或者 main 入口不存在或无法解析,则 Node.js 将会试图加载目录下的
index.js文件。 -
如果以上两步都失败了,则 Node.js 会在终端打印错误消息,报告模块的缺失:
Error: Cannot find module 'xxx'
第三方模块的加载机制
Node.js 会从当前模块的父 目录开始,尝试从
/node_modules文件夹中加载第三方模块。如果没有找到对应的第三方模块,则移动到
再上一层父目录中,进行加载,直到文件系统的根目录。
例如,假设在 'C:\Users\itheima\project\foo.js' 文件里调用了 require('tools'),则 Node.js 会按以下顺序查找:
- C:\Users\itheima\project\node_modules\tools
- C:\Users\itheima\node_modules\tools
- C:\Users\node_modules\tools
- C:\node_modules\tools
express
作用:和 Node.js 内置的
http模块类似,是专门用来创建 Web 服务器的。本质:就是一个 npm 上的第三方包,提供了快速创建 Web 服务器的
便捷方法。
基本使用
// 1. 导入 express
const express = require('express')
// 2. 创建 web 服务器
const app = express()
// 4. 监听客户端的 GET 和 POST 请求,并向客户端响应具体的内容
app.get('/user', (req, res) => {
// 调用 express 提供的 res.send() 方法,向客户端响应一个 `JSON 对象`
res.send({ name: 'zs', age: 20, gender: '男' })
})
app.post('/user', (req, res) => {
// 调用 express 提供的 res.send() 方法,向客户端响应一个 `文本字符串`
res.send('请求成功')
})
// 3. 启动 web 服务器
app.listen(80, () => {
console.log('express server running at http://127.0.0.1')
})
把内容响应给客户端
res.send()可以向客户端响应一个 JSON 对象或 文本字符串
获取 URL 中携带的查询参数
获取在地址栏中输入的参数 req.query

app.get('/', (req, res) => {
// 访问根路径
// 通过 req.query 可以获取到客户端发送过来的 查询参数
// 注意:默认情况下,req.query 是一个空对象
console.log(req.query)
res.send(req.query.id)// 1
})
获取 URL 中的动态参数
req.params 是动态匹配到的 URL 参数
// 注意:这里的 :id 是一个动态的参数
// ids--参数名;浏览器地址栏中--参数值
app.get('/user/:ids/:username', (req, res) => {
// req.params 是动态匹配到的 URL 参数,默认也是一个空对象
console.log(req.params)
res.send(req.params.ids)
})
express.static()
方便地创建一个
静态资源服务器注册静态资源文件夹 托管(可以处理 html/css/js/图片等类型文件)
// clock是文件夹,里面有index.html文件
// 在地址栏写入 http://127.0.0.1/index.html 访问该文件
app.use(express.static('./clock'))
托管多个静态资源目录
// 在第一个文件夹里找到了 文件,就不再去第二个文件夹找
app.use(express.static('./clock'))
app.use(express.static('./files'))
挂载路径前缀
// 在浏览器中 需要加上前缀/files
// http://127.0.0.1/files/index.html
app.use('/files', express.static('./files'))
nodemon
它能够
监听项目文件 的变动,当代码被修改后,nodemon 会自动帮我们重启项目,极大方便了开发和调试。
终端执行
# 安装包
npm i nodemon -g
# 开启项目
nodemon 文件名
Express路由
在 Express 中,路由指的是
客户端的请求与服务器处理函数之间的映射关系。Express 中的路由分 3 部分组成,分别是请求的类型、请求的 URL 地址、处理函数,格式:
app.method(path, handler)
路由匹配:请求方式+url --> 处理函数
模块化路由
router.js
// 这是路由模块
// 1. 导入 express
const express = require('express')
// 2. 创建路由对象
const router = express.Router()
// 3. 挂载具体的路由
router.get('/user/list', (req, res) => {
res.send('Get user list.')
})
router.post('/user/add', (req, res) => {
res.send('Add new user.')
})
// 4. 向外导出路由对象
module.exports = {
router
}
app.js
注意: 导入的路由 ,如果是对象,必须要解构出来,只需要router属性const {router} = require('./router.js')
const express = require('express')
const app = express()
// app.use('/files', express.static('./files'))
// 1. 导入路由模块 `解构`出router 属性
const {router} = require('./router.js')
// 2. 注册路由模块 添加统一的访问前缀 /api
app.use('/api', router)
// 注意: app.use() 函数的作用,就是来注册全局中间件
app.listen(80, () => {
console.log('http://127.0.0.1')
})
在地址栏中 输入 http://127.0.0.1/api/user/list 即可访问路由
Express 中间件
中间件(Middleware ),特指业务流程的中间处理环节。
全局生效中间件
app.use(中间件函数)
const express = require('express')
const app = express()
// 这是定义全局中间件的简化形式
app.use((req, res, next) => {
// 获取到请求到达服务器的时间
const time = Date.now()
// 为 req 对象,挂载自定义属性,从而把时间共享给后面的所有路由
req.startTime = time
next()
})
app.get('/', (req, res) => {
res.send('Home page.' + req.startTime)
})
app.get('/user', (req, res) => {
res.send('User page.' + req.startTime)
})
app.listen(80, () => {
console.log('http://127.0.0.1')
})
局部生效中间件
// 导入 express 模块
const express = require('express')
// 创建 express 的服务器实例
const app = express()
// 1. 定义中间件函数
const mw1 = (req, res, next) => {
console.log('调用了第一个局部生效的中间件')
next()
}
const mw2 = (req, res, next) => {
console.log('调用了第二个局部生效的中间件')
next()
}
// 2. 创建路由 中间件只对 该路由生效 可以不写[]
app.get('/', [mw1, mw2], (req, res) => {
res.send('Home page.')
})
app.get('/user', (req, res) => {
res.send('User page.')
})
// 调用 app.listen 方法,指定端口号并启动web服务器
app.listen(80, function () {
console.log('Express server running at http://127.0.0.1')
})
中间件的的分类
-
应用级别的中间件
- 通过 app.use() 或 app.get() 或 app.post(),绑定到
app 实例上的中间件
- 通过 app.use() 或 app.get() 或 app.post(),绑定到
-
路由级别的中间件
- 绑定到
express.Router() 实例上的中间件,叫做路由级别的中间件。 - 它的用法和应用级别中间件没有任何区别。只不 过,应用级别中间件是绑定到 app 实例上,路由级别中间件绑定到 router 实例上,代码示例如下:
- 绑定到
-
错误级别的中间件
- 错误级别中间件的作用:专门用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题。
- 格式:错误级别中间件的 function 处理函数中,必须有 4 个形参,形参顺序从前到后,分别是 (err, req, res, next)。
注意:错误级别的中间件, 必须注册在所有路由之后!
// 导入 express 模块
const express = require('express')
// 创建 express 的服务器实例
const app = express()
// 1. 定义路由
app.get('/', (req, res) => {
// 1.1 人为的制造错误
throw new Error('服务器内部发生了错误!')
// 抛出错误后,不会执行这行代码
res.send('Home page.')
})
// 2. 定义错误级别的中间件,捕获整个项目的异常错误,从而防止程序的崩溃
app.use((err, req, res, next) => {
console.log('发生了错误!' + err.message)// 发生了错误!服务器内部发生了错误!
res.send('对不起,页面崩溃了!请稍后再试~')
})
// 调用 app.listen 方法,指定端口号并启动web服务器
app.listen(80, function () {
console.log('Express server running at http://127.0.0.1')
})
- Express内置的中间件
-
express.static快速托管静态资源的内置中间件,例如: HTML 文件、图片、CSS 样式等(无兼容性) -
express.json解析 JSON 格式的请求体数据(有兼容性,仅在 4.16.0+ 版本中可用)- JSON格式的数据:数组和对象(且属性和字符串都要加"") 例:
{"name":"jean","age":17}或["jean",17]
- JSON格式的数据:数组和对象(且属性和字符串都要加"") 例:
-
express.urlencoded解析 URL-encoded 格式的请求体数据(有兼容性,仅在 4.16.0+ 版本中可用)- URL-encoded 格式的数据:键值对 例:name=jean&&age=17
- form.serilize(),就是键值对 格式
- 利用中间件,会解析为 对象格式
-
// 导入 express 模块
const express = require('express')
// 创建 express 的服务器实例
const app = express()
// 注意:除了错误级别的中间件,其他的中间件,必须在路由之前进行配置
// 通过 express.json() 这个中间件,解析表单中的 JSON 格式的数据
app.use(express.json())
// 通过 express.urlencoded() 这个中间件,来解析 表单中的 url-encoded 格式的数据
app.use(express.urlencoded({ extended: false }))
app.post('/user', (req, res) => {
// 在服务器,可以使用 req.body 这个属性,来接收客户端发送过来的请求体 JSON 格式的表单数据和 url-encoded 格式的数据
// 默认情况下,如果不配置解析表单数据的中间件,则 req.body 默认等于 undefined
console.log(req.body);
res.send('ok')
})
// 调用 app.listen 方法,指定端口号并启动web服务器
app.listen(80, function () {
console.log('Express server running at http://127.0.0.1')
})
- 第三方的中间件
需要npm下载,再require导入,再app.use()使用
不同格式数据的接收方式
使用CORS解决接口的跨域问题
JSONP只支持get请求
1. 安装cors
npm i cors
2. 导入,并配置中间件
// 一定要在路由之前,配置 cors 这个中间件,从而解决接口跨域的问题
const cors = require('cors');
app.use(cors())
CORS 响应头部
域名限制 - Access-Control-Allow-Origin
响应头部中可以携带一个 Access-Control-Allow-Origin 字
origin 参数的值指定了允许访问该资源的外域 URL。
Access-Control-Allow-Origin: <origin> | *
// 只允许来自 http://itcast.cn 的请求:
res.setHeader('Access-Control-Allow-Origin','http://itcast.cn')
// 示允许来自任何域的请求
res.setHeader('Access-Control-Allow-Origin','*')
字段限制 - Access-Control-Allow-Headers
默认情况下,CORS 仅支持客户端向服务器发送如下的 9 个请求头里带的字段:
Accept、Accept-Language、Content-Language、DPR、Downlink、Save-Data、Viewport-Width、Width 、 Content-Type (值仅限于 text/plain、multipart/form-data、application/x-www-form-urlencoded 三者之一)
如果客户端向服务器发送了额外的请求头信息,则需要在服务器端,通过 Access-Control-Allow-Headers 对额外 的请求头进行声明,否则这次请求会失败!
// 允许 客户端额外向服务器发送X-Custom-Header请求头
// 多个请求头 用,隔开
res.setHeader('Access-Control-Allow-Headers','Content-Type, X-Custom-Header')
请求方式 - Access-Control-Allow-Methods
默认情况下,CORS 仅支持客户端发起 GET、POST、HEAD 请求。
如果客户端希望通过 PUT、DELETE 等方式请求服务器的资源,则需要在服务器端,通过 Access-Control-Alow-Methods 来指明实际请求所允许使用的 HTTP 方法。
res.setHeader('Access-Control-Allow-Methods','POST,GET,DELETE,HEAD');
// 云去所有的 http 请求方式
res.setHeader('Access-Control-Allow-Methods','*')
简单请求和预检请求
- 简单请求
- 请求方式:GET、POST、HEAD 三者之一
- HTTP 头部信息的字段:无自定义头部字段
- 预检请求
在浏览器与服务器正式通信之前,浏览器会先发送 OPTION 请求进行预检,以获知服务器是否允许该实际请求,所以这一 次的 OPTION 请求称为“预检请求”。服务器成功响应预检请求后,才会发送真正的请求,并且携带真实数据。
- 请求方式为 GET、POST、HEAD 之外的请求 Method 类型
- 请求头中包含自定义头部字段
- 向服务器发送了 application/json 格式的数据
- 区别
-
简单请求的特点:客户端与服务器之间只会发生一次请求。
-
预检请求的特点:客户端与服务器之间会发生两次请求,OPTION 预检请求成功之后,才会发起真正的请求。
JSONP 接口
注意:如果项目中已经配置了 CORS 跨域资源共享,为了防止冲突,必须在配置 CORS 中间件之前声明 JSONP 的接口。否则 JSONP 接口会被处理成开启了 CORS 的接口
实现 JSONP 接口的步骤:
- 获取客户端发送过来的回调函数的名字
- 得到要通过 JSONP 形式发送给客户端的数据
- 根据前两步得到的数据,拼接出一个函数调用的字符串
- 把上一步拼接得到的字符串,响应给客户端的
<script>标签进行解析执行
使用express写接口
测试.html
<body>
<button id="btnGET">GET</button>
<button id="btnPOST">POST</button>
<button id="btnDelete">DELETE</button>
<button id="btnJSONP">JSONP</button>
<div></div>
<script>
$(function() {
// 1. 测试GET接口
$('#btnGET').on('click', function() {
$.ajax({
type: 'GET',
url: 'http://127.0.0.1/api/get',
data: {
name: 'zs',
age: 20
},
success: function(res) {
$('div').html(JSON.stringify(res));
console.log(res)
},
})
})
// 2. 测试POST接口
$('#btnPOST').on('click', function() {
$.ajax({
type: 'POST',
url: 'http://127.0.0.1/api/post',
data: {
bookname: '水浒传',
author: '施耐庵'
},
success: function(res) {
$('div').html(res);
console.log(res)
},
})
})
// 3. 为删除按钮绑定点击事件处理函数
$('#btnDelete').on('click', function() {
$.ajax({
type: 'DELETE',
url: 'http://127.0.0.1/api/delete',
success: function(res) {
$('div').text(res);
console.log(res)
},
})
})
// 4. 为 JSONP 按钮绑定点击事件处理函数
$('#btnJSONP').on('click', function() {
$.ajax({
type: 'GET',
url: 'http://127.0.0.1/api/jsonp',
dataType: 'jsonp',
success: function(res) {
$('div').html(res);
console.log(res)
},
})
})
})
</script>
</body>
app.js
// 导入 express
const express = require('express')
// 创建服务器实例
const app = express()
// 配置解析表单数据的中间件
app.use(express.urlencoded({ extended: false }))
// 必须在配置 cors 中间件之前,配置 JSONP 的接口
app.get('/api/jsonp', (req, res) => {
// TODO: 定义 JSONP 接口具体的实现过程
// 1. 得到函数的名称
const funcName = req.query.callback
// 2. 定义要发送到客户端的数据对象
const data = { name: 'zs', age: 22 }
// 3. 拼接出一个函数的调用
const scriptStr = `${funcName}(${JSON.stringify(data)})`
// 4. 把拼接的字符串,响应给客户端
res.send(scriptStr)
})
// 一定要在路由之前,配置 cors 这个中间件,从而解决接口跨域的问题
const cors = require('cors')
app.use(cors())
// 导入路由模块
const router = require('./router.js')
// 把路由模块,注册到 app 上
app.use('/api', router)
// 启动服务器
app.listen(80, () => {
console.log('express server running at http://127.0.0.1')
})
router.js
const express = require('express')
const router = express.Router()
// 在这里挂载对应的路由
router.get('/get', (req, res) => {
// 通过 req.query 获取客户端通过查询字符串,发送到服务器的数据
const query = req.query
// 调用 res.send() 方法,向客户端响应处理的结果
res.send({
status: 0, // 0 表示处理成功,1 表示处理失败
msg: 'GET 请求成功!', // 状态的描述
data: query, // 需要响应给客户端的数据
})
})
// 定义 POST 接口
router.post('/post', (req, res) => {
// 通过 req.body 获取请求体中包含的 url-encoded 格式的数据
const body = req.body
// 调用 res.send() 方法,向客户端响应结果
res.send({
status: 0,
msg: 'POST 请求成功!',
data: body,
})
})
// 定义 DELETE 接口
router.delete('/delete', (req, res) => {
res.send({
status: 0,
msg: 'DELETE请求成功',
})
})
module.exports = router
MySQL数据库
username: root password: admin123
port:3306默认
创建数据表 table
SQL的语句
注意:每一句 语句结束要写 ;
SELECT 语句
SELECT 语句用于从表中查询数据。执行的结果被存储在一个结果表中(称为结果集)。
注意:SQL的关键字对大小写不敏感,如select等价于SELECT
-- 从指定表中 查询 所有列的数据
SELECT * FROM 表名称
-- 从指定表中 查询 指定列名称(多个列用 , 隔开)的数据
SELECT 列名称 FROM 表名称
select username,password from users;
INSERT INTO 语句
向数据表中插入新的数据行
-- 列与值要一一对应
insert into 表名称 (列1,列2,...) values(值1,值2,...);
insert into users (username,password) values('jean','123456');
UPDATE 语句
修改表中的数据。
update 表名称 set 列名称 = 新值 where 列名称 = 某值
-- 1.修改某一行的某一列
-- 把 users 表中 id 为 2 的用户密码,更新为 000000
update users set password='000000' where id=2;
-- 2.更新某一行中的若干列
-- 把 users 表中 id 为 1 的password和status,分别更新为 admin123 和 1。
update users set password='admin123',status=1 where id=1
DELETE 语句
删除表中的行(一般是根据id-主键去删除)
delete form 表名称 where 列名称 = 值
-- 从 users 表中,删除 id 为 3 的用户
delete form users where id=3;
WHERE 子句
限定选择的标准。在 SELECT、UPDATE、DELETE 语句中,皆可使用 WHERE 子句来限定选择的标准。
-- 查询语句中的 where条件
select 列名称 from 表名称 where 列 运算符 值
-- 更新语句中的 where条件
update 表名称 set 列=新值 where 列 运算符 值
-- 删除语句中的 where条件
delete from 表名称 where 列 运算符 值
- where子句中使用的
运算符
| 操作符 | 描述 |
|---|---|
| = | 等于 |
| <>或!= | 不等于 |
| 大于 | |
| < | 小于 |
| >= | 大于等于 |
| <= | 小于等于 |
| BETWEEN | 在某个范围内 |
| LIKE | 搜索某种模式 |
| in | 选择多个 |
- where子句 示例
-- 通过 WHERE 子句来限定 SELECT 的查询条件:
-- 1.查询status为1的所有用户
select * from users where status=1
-- 2.查询id>1的所有用户
select * from users where id>1
-- 3.查询username不为zs的所有用户
select * from users where username<>'zs'
-- 将id为1和2的密码 都修改为000000
update users set password='000000' where id in (1,2)
AND 和 OR 运算符
示例
-- 删除id>=1且status=1的行
delete from users where id>=1 and status=1
ORDER BY 子句
ORDER BY 语句用于根据
指定的列对结果集进行排序。ORDER BY 语句
默认按照升序对记录进行排序。如果您希望按照
降序对记录进行排序,可以使用DESC关键字。
- **升序排序 **
-- 按照status按照升序排序
select * from users order by status;
select * from users order by status ASC;
- **降序排序 **
-- 按照id进行降序排序
select * from users order by id DESC;
- 多重排序
-- 先按照 status 字段进行降序排序,再按照 username 的字母顺序,进行升序排序
select * from users order by status desc,username asc;
COUNT(*) 函数
返回查询结果的总数据条数
select count(*) from 表名称
-- 查询users表中status为0 的总数据条数
select count(*) from users where status=0
-- 查询users表中gender`列不为空` 的总数据条数
select count(gender) from users
-- 等价于如下
select count(*) as gender from users where gender<>'';
使用 AS 为列设置别名
-- 将列名称从 count(*) 修改为total
select count(*) as total from users where status=0
在项目中操作mysql模块
下载模块
npm i mysql
配置与测试模块
// 1. 导入 mysql 模块
const mysql = require('mysql')
// 2. 建立与 MySQL 数据库的连接关系
const db = mysql.createPool({
host: '127.0.0.1', // 数据库的 IP 地址
user: 'root', // 登录数据库的账号
password: 'admin123', // 登录数据库的密码
database: 'my_db_01', // 指定要操作哪个数据库
})
// 测试 mysql 模块能否正常工作
db.query('select 1', (err, results) => {
// mysql 模块工作期间报错了
if(err) return console.log(err.message)
// 能够成功的执行 SQL 语句
console.log(results)// [ RowDataPacket { '1': 1 } ]
})
查询数据
// 查询 users 表中 gender="女"所有的数据
const sqlStr = 'select * from users where gender="女"';
db.query(sqlStr, (err, results) => {
// 查询数据失败
if (err) return console.log(err.message)
// 查询数据成功
// 注意:如果执行的是 select 查询语句,则执行的结果是数组
console.log(results)// 返回的是数组
})
插入数据
使用?占位符,是为了避免spq注入攻击
// 向 users 表中,新增一条数据,其中 username 的值为 Spider-Man,password 的值为 pcc123
const user = { username: 'Spider-Man', password: 'pcc123' };
// 定义待执行的 SQL 语句 ??是占位,可以通过数据为其指定值
const sqlStr = 'insert into users (username, password) values (?, ?)';
// 执行 SQL 语句
db.query(sqlStr, [user.username, user.password], (err, results) => {
// 执行 SQL 语句失败了
if (err) return console.log(err.message);
// 成功了
// 注意:如果执行的是 insert into 插入语句,则 results 是一个对象
// 可以通过 affectedRows 属性,来判断是否插入数据成功
if (results.affectedRows === 1) {
console.log('插入数据成功!')
}
})
插入数据的便捷方式
const user = { username: 'Spider-Man2', password: 'pcc4321' };
// 定义待执行的 SQL 语句
const sqlStr = 'insert into users set ?';
// 执行 SQL 语句
db.query(sqlStr, user, (err, results) => {
if (err) return console.log(err.message)
if (results.affectedRows === 1) {
console.log('插入数据成功')
}
})
更新数据
const user = { id: 6, username: 'aaa', password: '000' };
// 定义 SQL 语句
const sqlStr = 'update users set username=?, password=? where id=?';
// 执行 SQL 语句
db.query(sqlStr, [user.username, user.password, user.id], (err, results) => {
if (err) return console.log(err.message);
// 注意:执行了 update 语句之后,执行的结果,也是一个对象,可以通过 affectedRows 判断是否更新成功
if (results.affectedRows === 1) {
console.log('更新成功')
}
})
更新数据的便捷方式
// 演示更新数据的便捷方式
const user = { id: 6, username: 'aaaa', password: '0000' };
// 定义 SQL 语句
const sqlStr = 'update users set ? where id=?';
// 执行 SQL 语句
db.query(sqlStr, [user, user.id], (err, results) => {
if (err) return console.log(err.message)
if (results.affectedRows === 1) {
console.log('更新数据成功')
}
})
删除用户
// 删除 id 为 5 的用户
const sqlStr = 'delete from users where id=?';
db.query(sqlStr, 5, (err, results) => {
if (err) return console.log(err.message);
// 注意:执行 delete 语句之后,结果也是一个对象,也会包含 affectedRows 属性
if (results.affectedRows === 1) {
console.log('删除数据成功')
}
})
标记删除
使用 DELETE 语句,会把真正的把数据从表中删除掉。为了保险起见,推荐使用
标记删除的形式,来模拟删除的动作。所谓的标记删除,就是在表中设置类似于
status这样的状态字段,来标记当前这条数据是否被删除。当用户执行了删除的动作时,我们并没有执行 DELETE 语句把数据删除掉,而是执行了 UPDATE 语句,将这条数据对应 的 status 字段标记为删除即可。
const sqlStr = 'update users set status=? where id=?';
db.query(sqlStr, [1, 6], (err, results) => {
if (err) return console.log(err.message)
if (results.affectedRows === 1) {
console.log('标记删除成功')
}
})
前后端的身份验证
Web 开发模式
- 基于
服务端渲染的传统 Web 开发模式 - 基于
前后端分离的新型 Web 开发模式
服务端渲染的 Web 开发模式
服务器发送给客户端的 HTML 页面,是在服务器通过字符串的拼接,动态生成的。因此,客户端不 需要使用 Ajax 这样的技术额外请求页面的数据。
优点:
- 前端耗时少。因为服务器端负责动态生成 HTML 内容,浏览器只需要直接渲染页面即可。尤其是移动端,更省电。
- 有利于SEO。因为服务器端响应的是完整的 HTML 页面内容,所以爬虫更容易爬取获得信息,更有利于 SEO
缺点:
- 占用服务器端资源。即服务器端完成 HTML 页面内容的拼接,如果请求较多,会对服务器造成一定的访问压力。
- 不利于前后端分离,开发效率低。使用服务器端渲染,则无法进行分工合作,尤其对于前端复杂度高的项目,不利于 项目高效开发。
前后端分离的 Web 开发模式
前后端分离的开发模式,依赖于 Ajax 技术的广泛应用。
即后端不提供完整的 HTML 页面内容,而 是提供一些 API 接口,使得前端可以获取到 json 数据;然后前端通过 Ajax 调用后端提供的 API 接口,拿到 json 数据 之后再在前端进行 HTML 页面的拼接,最终展示在浏览器上。
简而言之,前后端分离的 Web 开发模式,就是
后端只负责提供 API 接口,前端使用 Ajax 调用接口的开发模式。
优点:
开发体验好。前端专注于 UI 页面的开发,后端专注于api 的开发,且前端有更多的选择性。用户体验好。Ajax 技术的广泛应用,极大的提高了用户的体验,可以轻松实现页面的局部刷新。- 减轻了服务器端的渲染压力。因为页面最终是在每个用户的浏览器中生成的。
缺点:
不利于 SEO。因为完整的 HTML 页面需要在客户端动态拼接完成,所以爬虫对无法爬取页面的有效信息。(解决方 案:利用 Vue、React 等前端框架的 SSR (server side render)技术能够很好的解决 SEO 问题!)
如何选择开发模式
-
比如企业级网站,主要功能是展示而没有复杂的交互,并且需要良好的 SEO,则这时我们就需要使用服务器端渲染;
-
而类似后台管理页面,交互性比较强,不需要 SEO 的考虑,那么就可以使用前后端分离的开发模式。
-
另外,具体使用何种开发模式并不是绝对的,为了同时兼顾了首页的渲染速度和前后端分离的开发效率,一些网站采用了 首屏服务器端渲染,即对于用户最开始打开的那个页面采用的是服务器端渲染,而其他的页面采用前后端分离开发模式。
身份认证
**不同开发模式下的身份认证 **
-
服务端渲染推荐使用
Session认证机制 -
前后端分离推荐使用
JWT认证机制
Session 认证机制
HTTP 协议的
无状态性每次的 HTTP 请求都是
独立的,连续多个 HTTP 请求之间没有直接的关系,服务器不会 主动保留每次 HTTP 请求的状态。
cookie
医院病例,病例拿在自己手里(cookie存在 浏览器端)
Cookie 是存储在用户浏览器中的一段
不超过 4 KB 的字符串。它由一个名称(Name)、一个值(Value)和其它几个用 于控制 Cookie 有效期、安全性、使用范围的可选属性组成。
不同域名下的 Cookie各自独立,每当客户端发起请求时,会自动把当前域名下所有未过期的 Cookie 一同发送到服务器。
Cookie的几大特性:
- 自动发送
- 域名独立
- 过期时限
- 4KB限制
Session 的工作原理
银行机制,钱存在银行里(sesssion存在 服务器端)
Session 认证的局限性
Session 认证机制需要配合 Cookie 才能实现。由于 Cookie 默认不支持跨域访问,所以,当涉及前端跨域请求后端接口的时候,需要做很多额外的配置,才能实现跨域 Session 认证。
且客户端的app或小程序 是不支持cookie的!
在Express 中使用 Session 认证
npm i express-session
// 导入 express 模块
const express = require('express')
// 创建 express 的服务器实例
const app = express()
// TODO_01:请配置 Session 中间件
const session = require('express-session')
app.use(
session({
secret: 'itheima', // 盐值(用来增加 加密的复杂度--注意:解密也需要用到相同的盐值)
resave: false,
saveUninitialized: true,
})
)
// 托管静态页面
app.use(express.static('./pages'));
// 解析 POST 提交过来的表单数据
app.use(express.urlencoded({ extended: false }));
// 登录的 API 接口
app.post('/api/login', (req, res) => {
// 判断用户提交的登录信息是否正确
if (req.body.username !== 'admin' || req.body.password !== '000000') {
return res.send({ status: 1, msg: '登录失败' })
}
// TODO_02:请将登录成功后的用户信息,保存到 Session 中
// 注意:只有成功配置了 express-session 这个中间件之后,才能够通过 req 点出来 session 这个属性
req.session.user = req.body // 用户的信息
req.session.islogin = true // 用户的登录状态
res.send({ status: 0, msg: '登录成功' })
})
// 获取用户姓名的接口
app.get('/api/username', (req, res) => {
// TODO_03:请从 Session 中获取用户的名称,响应给客户端
if (!req.session.islogin) {
return res.send({ status: 1, msg: 'fail' })
}
res.send({
status: 0,
msg: 'success',
username: req.session.user.username,
})
})
// 退出登录的接口
app.post('/api/logout', (req, res) => {
// TODO_04:清空 Session 信息
// 注意:销毁的是当前 浏览器ip的session 不会影响其他域名的session
req.session.destroy()
res.send({
status: 0,
msg: '退出登录成功',
})
})
// 调用 app.listen 方法,指定端口号并启动web服务器
app.listen(80, function() {
console.log('Express server running at http://127.0.0.1:80')
})
JWT 认证机制
JWT(英文全称:JSON Web Token)是目前最流行的跨域认证解决方案。
用户的信息通过 Token 字符串的形式,保存在客户端浏览器中。服务器通过还原 Token 字符串的形式来认证用户的身份。
JWT 的组成部分
JWT 通常由三部分组成,分别是 Header(头部)、Payload(有效荷载)加密、Signature(签名)。三者之间使用英文的“.”分隔
-
Payload部分才是真正的用户信息,它是用户信息经过加密之后生成的字符串。 -
Header 和 -Signature 是
安全性相关的部分,只是为了保证 Token 的安全性。
JWT 的使用方式
客户端收到服务器返回的 JWT 之后,通常会将它储存在 localStorage 或 sessionStorage 中。
此后,客户端每次与服务器通信,都要带上这个 JWT。推荐的做法是把 JWT 放在 HTTP 请求头的 Authorization 字段 中
Authorization: Bearer <token>
在 Express 中使用 JWT
# 安装两个包
npm i jsonwebtoken express-jwt
# jsonwebtoken 用于生成 JWT 字符串
# express-jwt 用于将 JWT 字符串解析还原成 JSON 对象
app.js
// 导入 express 模块
const express = require('express')
// 创建 express 的服务器实例
const app = express()
// TODO_01:安装并导入 JWT 相关的两个包,分别是 jsonwebtoken 和 express-jwt
const jwt = require('jsonwebtoken')
const expressJWT = require('express-jwt')
// 允许跨域资源共享
const cors = require('cors')
app.use(cors())
// 解析 post 表单数据的中间件
const bodyParser = require('body-parser')
app.use(bodyParser.urlencoded({ extended: false }))
// TODO_02:定义 secret 密钥,建议将密钥命名为 secretKey
const secretKey = 'itheima No1 ^_^'
// TODO_04:注册将 JWT 字符串解析还原成 JSON 对象的中间件
// 注意:只要配置成功了 express-jwt 这个中间件,就可以把解析出来的用户信息,挂载到 req.user 属性上
// .unless()--指定哪些接口 不需要访问权限
app.use(expressJWT({ secret: secretKey }).unless({ path: [/^\/api\//] }))
// 登录接口
app.post('/api/login', function(req, res) {
// 将 req.body 请求体中的数据,转存为 userinfo 常量
const userinfo = req.body
// 登录失败
if (userinfo.username !== 'admin' || userinfo.password !== '000000') {
return res.send({
status: 400,
message: '登录失败!',
})
}
// 登录成功
// TODO_03:在登录成功之后,调用 jwt.sign() 方法生成 JWT 字符串。并通过 token 属性发送给客户端
// 参数1:用户的信息对象
// 参数2:加密的秘钥
// 参数3:配置对象,可以配置当前 token 的有效期--- 如果解密的时间超过了30s,这个tokenStr就作废了
// 记住:千万不要把密码加密到 token 字符中
const tokenStr = jwt.sign({ username: userinfo.username }, secretKey, { expiresIn: '30s' })
res.send({
status: 200,
message: '登录成功!',
token: tokenStr, // 要发送给客户端的 token 字符串
})
})
// 这是一个有权限的 API 接口
app.get('/admin/getinfo', function(req, res) {
// TODO_05:使用 req.user 获取用户信息,并使用 data 属性将用户信息发送给客户端
console.log(req.user)
res.send({
status: 200,
message: '获取用户信息成功!',
data: req.user, // 要发送给客户端的用户信息
})
})
// TODO_06:使用全局错误处理中间件,捕获解析 JWT 失败后产生的错误
app.use((err, req, res, next) => {
// 这次错误是由 token 解析失败导致的
if (err.name === 'UnauthorizedError') {
return res.send({
status: 401,
message: '无效的token',
})
}
res.send({
status: 500,
message: '未知的错误',
})
})
// 调用 app.listen 方法,指定端口号并启动web服务器
app.listen(8888, function() {
console.log('Express server running at http://127.0.0.1:8888')
})
ES6 模块化
需要在 package.json 中 加上 "type": "module"
导出与导入
默认
注意:
-
只能
默认导出一次 -
默认导入的命名,遵循变量/常量声明的规则,本质是
常量 -
导入时使用
解构语法获取特定的属性,需要配合挨个按需导出使用
# 01.js
// 默认导出
let name='jean';
let age=17;
function run(){};
export default {
name,
age,
run
}
# 02.js
// 默认导入
import s1 from '文件相对路径'
s1是一个常量对象,不能改其引用
按需
注意:
-
按需导出可以使用多次 -
导出与导出的名称必须
一致
# 01.js
export let name='jean';
export let age=17;
export function say(){};
export default {
a:20
}
# 02.js
import info, {name, age as ageNum, say} from './01.js';
// 将age重命名为ageNum
info---{a:20}
直接导入 并执行模块中的代码
#01.js
for(let i=0;i<5;i++){
console.log(i);
}
#02.js
import './01.js'
Promise(ES6新)
Promise是一个
构造函数---const p = new Promise()---实例是一个异步操作作用:解决 由于回调函数 嵌套出现的“回调地狱”
本质是
解耦Promise.prototype 有一个
.then方法,then默认 会返回一个新的promise对象
let p = new Promise(function(successCB, errorCB) {
let age=19;
if (i > 18) successCB('可以喝酒');
else errorCB('不能喝酒');
});
//p.then(
// success => { console.log(success); },
// err => { console.log(err); }
//);
p.then(
success => { console.log(success); },
).catch(
err => { console.log(err); });
// 结果:可以喝酒
Promise.then().catch
安装
npm i then-fs
注意:
-
如果then中直接return 非promise对象,会自动创建一个promise对象
-
如果多个promise执行,
最后添加的唯一一个catch,前面所有的promise对象都共用该错误回调函数- 一旦前面的promise对象出现错误,会直接进入到最后catch中运行,中间的then将不再执行
-
如果不希望后面的代码无法执行,可以把.catch往前移
import fs from 'then-fs';
fs.readFile('./files/01.js', 'utf8')
.catch(err=>{
console.log('找不到文件');
})
.then(r1 => {
console.log(r1);
return fs.readFile('./files/02.txt', 'utf8'); // 返回一个新的promise实例对象
}).then(r2 => {
console.log(r2);
return fs.readFile('./files/03.txt', 'utf8');
}).then(r3 => {
console.log(r3);
return '自动生成一个promise对象';
}).then(msg => {
console.log(msg);
})
// 结果: 找不到文件 undefined 2 3 自动生成一个promise对象
Promise.all() | .race()
import fs from 'then-fs';
let p1 = fs.readFile('./files/1.txt','utf8');
let p2 = fs.readFile('./files/2.txt','utf8');
let p3 = fs.readFile('./files/3.txt','utf8');
# 1. all()
// 3个任务都按顺序成功执行,才调用then 中的回调函数
Promise.all([p1, p2, p3])
.then(function(res) {
console.log(res);// 输出的是数组['1','2','3']
}).catch(function(err) {
console.log(err);
})
# 2. race()
// 只要有一个任务成功执行了,就调用then函数
Promise.race([p1, p2, p3])
.then(function(res) {
console.log(res);// 1
}).catch(function(err) {
console.log(err);
})
基于promise封装读文件的方法
import fs from 'fs';
const myfs = {}
myfs.readFile = function(filepath, charset) {
return new Promise(function(resolve, reject) {
// 使用 内置 fs模块读取文件
fs.readFile(filepath, charset, (err, data) => {
// 读取失败,调用失败回调函数,并传入错误消息
if (err) return reject(err);
// 读取成功,调用成功回调函数,并传入文件内容
resolve(data);
})
});
}
myfs.readFile('./files/01.txt', 'utf8').then(data => {
console.log('文件读取成功------');
console.log(data);
}).catch(err => {
console.log('文件读取失败------');
console.log(err);
})
async/await(ES8新)
注意:第一个 await 前的代码是同步执行,后面的代码都会异步按顺序执行
import fs from 'then-fs';
console.log('a');
async function ss() {
console.log('b');
const r1 = await fs.readFile('./files/01.txt', 'utf8');
const r2 = await fs.readFile('./files/02.txt', 'utf8');
const r3 = await fs.readFile('./files/03.txt', 'utf8');
console.log('c');
}
ss();
console.log('d');
// a b d c
EventLoop
同步任务(synchronous)
- 由JavaScript主线程(js引擎)次序执行
异步任务(asynchronous)
- 委托给
宿主环境(操作系统--浏览器)执行