0 说明
node起步于2009年5月,node基金会成立于2015年,快速获得各大厂商的支持并引入es6
笔记相关github代码
笔记相关b站视频链接
b站相关的黑马前端路线链接
node部分内容与07 ajax重合,快速掠过
看完后感觉是目前的几套前端视频教程里基础步骤讲的最细致的。堪称从入门到精通(入门)教程的典范
1 初识node内置模块
Chrome浏览器的v8引擎负责解析和执行JavaScript代码。
浏览器的内置API(dom,bom,canvas,XMLHttpRequest,ajax,有关JS的一些内置对象等等)是由浏览器这个运行环境提供的接口,这些API只能在所属的运行环境(浏览器,对比之下,node不是这些api需要的运行环境,node用不了这些API接口)中被调用。
Node是基于Chrome浏览器的v8引擎的js(后端)运行环境:
v8引擎+内置API(fs,path,http,有关js的一些内置对象,querstring等)
1.1 内置模块-fs
const fs = require(‘fs’)
fs.readFile()/writeFile()
1.2 内置模块-path
__dirname:解释器当前目录
path.join():合并路径,不受./
中的.
的影响
path.basename():获取路径下的文件名
path.extname():获取拓展名
1.3 内置模块-http
标准流程3步走:
const http = require("http")
// 1 创建web服务实例
const server = http.createServer()
// 2 绑定request事件
server.on('request',(req,res)=>{
console.log('require visit');
})
// 3 启动
server.listen(8080,()=>{
console.log('启动成功');
})
1.4 模块化
- 能够说出 Node.js 中模块的三大分类各自是什么:
内置,第三方和自定义模块 - 能够知道 CommonJS 规定了哪些内容&能够了解模块的加载机制
CommonJS规范规定,每个模块内部,module变量代表当前模块。
这个变量是一个对象,它的exports属性(即module.exports)是对外的接口。
加载某个模块,其实是加载该模块的module.exports属性。
所有代码都运行在模块作用域,不会污染全局作用域。
模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
模块加载的顺序,按照其在代码中出现的顺序。
上面两段参考帖
exports和module.exports的导出指向一致,但是require导入加载的最终指向module.exports
2 npm 包管理
- 能够了解什么是规范的包结构
1、必须是单独的文件夹目录
2、顶级目录下必须有package.json包管理文件
3、package.json中必须包含属性:name,version,main(main属性指向包的入口文件,不写默认指向index.js文件)
包的实质是内置模块的封装
安装:npm i moment@2.22.2
项目初始化后运行:npm init -y
, 自动配置package.json,github pulld的新项目需要先运行npm i
安装需要的包环境;
卸载:npm uninstall 包名字 - 关于包的发布可以看视频跟着流程走就行,关键是封装,最关键是不要发布无意义的包
- 包的缓存机制,require加载后会缓存
自定义模块导入必须加./
的路径标识符,尽量加拓展名
3 web基础
3.1 express
Express是基于node.js的快速开放极简的web开发框架,类似node内置的http模块
nodemon可以取代node命令动态渲染保存后的服务端代码
3.1.1 express.static托管静态资源
借助use托管static资源
const express = require('express')
const app =express()
app.use('/files',express.static('./files'))
app.use(express.static('./clock'))
app.listen(80,()=>{
console.log('启动成功,在127.0.0.1');
})
3.1.2 express路由精简项目结构
路由指客户端请求与服务器处理函数之间的映射关系
express的路由分3部分组成:请求类型,请求URL,服务器端处理函数
- 模块化路由
express不建议路由直接挂在到app上,推荐将路由抽离为单独的模块:
1、创建单独的模块js文件
2、调用express.Router()创建路由对象
3、路由对象上挂载路由
4、使用module.exports共享路由对象
5、在别的文件中,app.use()注册路由模块,使用上类似vue。
app.use(‘/前缀’,router)作用是注册全局中间件,
3.1.3 express常见的中间件
路由和中间件函数的区别就是中间件函数参数里是否包含next
参数,并利用use方法注册配置中间件
// 中间件有next参数
app.use((req,res,next)=>{
const time = Date.now()
// req对象挂在自定义属性
req.startTime = time
// 中间件利用next方法返回数据
next()
})
- 局部中间件
// 定义中间件函数
const mw1 = (req,res,next)=>{
console.log('调用了局部生效的中间件');
next()
}
// 创建路由,局部中间件多一个参数
app.get('/',[mw1],(req,res)=>{
res.send('home page')
})
- express的5类中间件
1、应用级别
直接绑定到app.use/get/post实例上的中间件
2、路由级别
绑定到抽离封装好的router上的中间件
3、错误级别
必须有4个形参(err,req,res,next),错误中间件要放在所有定义的路由的后边
// 1 定义路由
app.get('/',(req,res)=>{
throw new Error('服务器内部发生了错误')
// throw 之后直接退出路由
res.send('home page')
})
// 2 错误级别中间件,在所有路由的后边来捕获错误
app.use((err,req,res,next)=>{
console.log('发生了错误:'+err.message);
res.send('error:'+err.message)
})
4、express内置
express.static静态/json请求体(4.16版本以上)/urlencoded({extended:false})
// 除了错误级别中间件放在注册路由之后,其他的中间件都放在路由前边
// 通过 express.json() 这个中间件,解析表单中的 JSON 格式的数据
app.use(express.json())
// 通过 express.urlencoded() 这个中间件,来解析 表单中的 url-encoded 格式的数据
app.use(express.urlencoded({ extended: false }))
app.post('/user',(req,res)=>{
// 在服务器,可以使用 req.body 这个属性,来接收客户端发送过来的请求体数据
// 默认情况下,如果不配置解析表单数据(postman设置body-raw-json)的中间件(express.json()),则 req.body 默认等于 undefined
console.log(req.body);
res.send('ok')
})
app.post('/book',(req,res)=>{
// 在服务器端,可以通过 req.body 来获取 JSON 格式的表单数据和 url-encoded 格式(postman里的x-www-form-urlencoded)的数据
console.log(req.body);
res.send('ok')
})
5、第三方
4.16版本以前用body-parser这个第三方中间件,之后版本的express内置的express.urlencoded就是基于body-parser封装出来的。
3.1.4 express启用cors跨域资源共享
cross origin resource sharing
跨域问题:协议,域名,端口不一致导致,响应头处理
处理:cors或者jsonp(浏览器通过通过
- 服务器端配置开启cors即可
- XMLHttpRequest Level2浏览器才行:ie10+,Chrome4+,firefox3.5+
- cors响应头设置:
1、Access-Control-Allow-Origin: | *
如果指定了 Access-Control-Allow-Origin 字段的值为通配符 *,表示允许来自任何域的请求
res.setHeader('Access-Control-Allow-Origin','http://itcast.cn'|'*')
2、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 对额外的请求头进行声明,否则这次请求会失败!
3、Access-Control-Allow-Methods
默认情况下,CORS 仅支持客户端发起 GET、POST、HEAD 请求(这3个是简单请求,除此之外都是预检请求——包含自定义头部字段或者包括application/json格式的数据)。
如果客户端希望通过 PUT、DELETE 等方式请求服务器的资源,则需要在服务器端,通过 Access-Control-Alow-Methods来指明实际请求所允许使用的 HTTP 方法。
// 允许所有
res.setHeader('Access-Control-Allow-Methods','*')
4 Mysql
4.1 mysql配置及基本语句
本部分sql语句适用于关系型数据库,mogodb等非关系型数据库不同
sql的关键词不区分大小写,字段名区分
- 插入数据 insert into:
insert into users (username,password) values ("zs","abc123")
- 修改 update :
update users set password="88888888",status = 1 where id = 7
- where结合and和or条件查询,order by(desc)排序
4.2 express操作mysql
安装插件:npm i mysql
如果测试连接报错提示权限有问题:ER_NOT_SUPPORTED_AUTH_MODE: Client does not support authentication protocol requested by server; consider upgrading MySQL client
可以尝试先设置以下sql语句:
ALTER USER 'your username'@'localhost' IDENTIFIED WITH mysql_native_password BY 'your password';
建立好mysql的db连接后,预先编写sql语句,传入db.query()方法执行
实际中删除常为逻辑删除,比如账号注销后,重新申请开通后还有原来的数据:
const sqlStr4 = '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('标记删除成功')
}
})
4.3 身份认证
- 开发模式
1、服务端渲染,直接发送给客户端的html标签,客户端不需要ajax额外请求数据。前端耗时少,有利用seo,但是不利于前后端分离,占用服务器资源,
2、前后端分离 - 身份认证
1、服务端渲染 :session认证
2、前后端分离:jwt认证
4.3.1 session
npm i express-session
session基于cookie,cookie因为存储在浏览器中不安全而只建议保存基础信息。为了避免伪造,需要每次请求都要在服务器端验证。
配置 Session 中间件
const session = require('express-session')
app.use(
session({
secret: 'itheima',
resave: false,
saveUninitialized: true,
})
)
session保存req.body里的信息
req.session.user = req.body // 用户的信息
req.session.islogin = true // 用户的登录状态
res.send({ status: 0, msg: '登录成功' })
4.3.2 JWT
json web token,跨域认证解决方案
npm i jsonwebtoken express-jwt
登录前准备验证,app.use
// 定义 secret 密钥,建议将密钥命名为 secretKey
const secretKey = 'itheima No1 ^_^'
// 注册将 JWT 字符串解析还原成 JSON 对象的中间件
// 注意:只要配置成功了 express-jwt 这个中间件,就可以把解析出来的用户信息,挂载到 req.user 属性上.unless排除不需要验证权限的接口
app.use(expressJWT({ secret: secretKey }).unless({ path: [/^\/api\//] }))
登录成功后做验证token并发送回客户端,jwt.sgin
// 记住:千万不要把密码加密到 token 字符中,用户名即可
const tokenStr = jwt.sign({ username: userinfo.username }, secretKey, { expiresIn: '30h' })
res.send({
status: 200,
message: '登录成功!',
token: tokenStr, // 要发送给客户端的 token 字符串
})
jwt错误捕获
// 使用全局错误处理中间件,捕获解析 JWT 失败后产生的错误
app.use((err, req, res, next) => {
// 这次错误是由 token 解析失败导致的
if (err.name === 'UnauthorizedError') {
return res.send({
status: 401,
message: '无效的token',
})
}
res.send({
status: 500,
message: '未知的错误',
})
})
5 案例 api_server
视频评论区推荐笔记
视频里黑马的接口文档链接打不开了,根据上面的笔记步骤看视频抄代码,重点是理顺代码文件目录之间的封装调用关系。注释比代码重要,本部分笔记多为摘抄,外加自己部分代码。
有些新手不知道如何可视化操作数据库表,直接附上sql建表:
-- 用户信息表
create table `ev_users` (
`id` int not null auto_increment,
`username` varchar(255) not null,
`password` varchar(255) not null,
`nickname` varchar(255) null,
`email` varchar(255) null,
`user_pic` TEXT null,
primary key(`id`),
unique index `id_unique` (`id` asc) visible,
unique index `username_unique` (`username` asc) visible
) comment = '用户信息表';
-- 文章分类数据表
create table `ev_article_cate` (
`id` int not null auto_increment,
`name` varchar(255) not null,
`alias` varchar(255) not null,
`is_delete` tinyint(1) not null default 0 comment '0 没有被删除,1 被删除',
primary key(`id`),
unique index `id_unique` (`id` asc) visible,
unique index `name_unique` (`name` asc) visible,
unique index `alias_unique` (`alias` asc) visible
) comment = '文章分类数据表';
-- 插入数据
insert into ev_article_cate (name,alias) values ('科技','KeJi'),('历史','LiShi');
-- 文章表
create table `ev_articles` (
`id` int not null auto_increment,
`title` varchar(255) not null,
`content` TEXT not null,
`cover_img` varchar(255) not null,
`pub_date` varchar(255) not null,
`state` varchar(255) not null,
`is_delete` tinyint(1) not null default 0 comment '0 没有被删除,1 被删除',
`cate_id` int not null,
`author_id` int not null,
primary key(`id`),
unique index `id_unique` (`id` asc) visible
) comment = '文章表';
5.1 登录注册
5.1.1 工具包作用介绍
@hapi/joi:定义验证规则的对象
@escook/express-joi:局部中间件验证上面定义好的对象
bcryptjs:密码加密
cors,:跨域请求
express:服务器框架
mysql:存储数据
jsonwebtoken:登录时服务端生成jwt的token
express-jwt:客户端可配置token发送
5.1.2 要点
1、密码调用bcrypt.hashSync() 对密码进行加密再储存
2、利用中间件封装res.cc方法
3、表单权限验证后端要保证兼容性
4、express里不允许在app.use里写两次res.send
5、jwt的token生成时:
// 对用户的信息进行加密,生成 jwt-Token 字符串(3个参数:用户信息,加密和解密 Token 的秘钥,有效期)
const tokenStr = jwt.sign(user, config.jwtSecretKey, { expiresIn: config.expiresIn })
5.2 个人中心
5.2.1 获取基本信息
1、router/userinfo.js 挂载路由,app.js下注册路由,测试时需要在header里携带登录时生成的Authorization:jwt-token
2、7以上版本的express-jwt里获取id(注册登录时jwt-token里含有id,自动挂载到请求对象req上)的req.user.id 替换成req.auth.id
3、// 定义sql语句,虽然可以select *然后结构给查询结果中的password赋值为0,为了安全,直接不查询加密后的password的token
const sql = `select id, username, nickname, email, user_pic from ev_users where id=?`
5.2.2 更新信息
1、schema/use.js里 定义 id, nickname, email 的验证规则,并向外导出验证规则对象
2、route_handler/userinfo.js里 编写更新用户基本信息的处理函数
3、router/userinfo.js里 挂载个人信息验证router
5.2.3 重置密码
1、定义路由和处理函数类似上面
2、验证表单数据对象逻辑:
newPwd: joi.not(joi.ref('oldPwd')).concat(password),
3、实现重置密码的功能,注意旧密码比较的是req.body.oldPwd加密后的token和数据库select的token是否正确,然后再把新密码加密后的token更新进数据库
5.2.4 更换头像
1、 /schema/user.js 验证规则模块中,定义 avatar 的验证规则
// dataUri() 指的是如下格式的base64字符串数据:
// data:image/png;base64,VE9PTUFOWVNFQ1JFVFM=
const avatar = joi.string().dataUri().required()
5.3 文章分类
5.3.1 获取文章分类
实现步骤
初始化路由模块
初始化路由处理函数模块
获取文章分类列表数据
5.3.2 新增文章分类
实现步骤
定义路由和处理函数
验证表单数据
查询 分类名称 与 分类别名 是否被占用
实现新增文章分类的功能
5.3.3 根据id删除
实现步骤
定义路由和处理函数
验证表单数据
实现删除文章分类的功能
5.3.4 根据id获取
实现步骤
定义路由和处理函数
验证表单数据
实现获取文章分类的功能
5.3.5 根据id更新
实现步骤
定义路由和处理函数
验证表单数据
查询 分类名称 与 分类别名 是否被占用
实现更新文章分类的功能
5.4 发布文章
实现步骤
初始化路由模块
初始化路由处理函数模块
使用 multer 解析表单数据
验证表单数据
实现发布文章的功能