08 Node.js 从入门到精通(入门)

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 解析表单数据
验证表单数据
实现发布文章的功能

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值