昔洛 的个人博客

Bug不空,誓不成佛

  menu
70 文章
14633 浏览
6 当前访客
ღゝ◡╹)ノ❤️

NodeJS 后端编程(下)

学习内容

  • express框架的使用方式
    • 静态资源托管
    • get请求接口
    • post请求接口
    • 中间件
  • 文件上传 multer
    • string-random 随机字符串
  • express中的路由管理方式
  • cookie和session
  • 跨域
    • cors
    • jsonp
  • 数据库
    • 基本语句
    • 使用 nodejs 操作数据库
  • 异步操作管理
    • promise
    • async await

express 框架的使用方式

  • 静态资源托管
    • 默认根目录访问时的托管
      app.use(express.static(path.join(__dirname, '书写一个目录名即可')))
    • 自定义地址的托管
      app.use('用来设置访问时的url' ,express.static(path.join(__dirname, '书写一个目录名即可')))
  • 设置 get 请求接口
    • 基本格式:app.get(请求地址, 回调函数)
      • 回调函数内部接收req和res与node中是一样的
      • req.query可以直接接收get请求参数的对象结构,无需借助其他模块进行转换
      • 响应方式:res.send() 通用功能,发送中文不会乱码,因为express设置了默认响应头为 text/html;charset=utf-8,发送对象会自动转换为JSON格式的字符串,会设置响应头为 application/json;charset=utf-8
  • 设置 post 请求接口
    • 基本格式:app.post(请求地址, 回调函数)
    • express中没有直接进行post请求参数的处理操作,需要使用与express相关的包进行post请求参数的处理操作使用的包名为 body-parser, 下载express时已经自动下载完毕了,可以直接进行引入操作
    • 引入方式:const bodyParser = require('body-parser');
    • 让app使用bodyParser的功能进行req.body的设置
      • app.use(bodyParser.urlencoded({extended: false}));
        • false表示使用内置模块querystring进行处理,true为默认值使用第三方模块qs处理
      • 可以在app.post()的回调函数中直接使用req.body进行请求体的操作
  • 中间件
    • 统一的中间件设置方式(所有请求都经过这个中间件的处理)
    	app.use((req, res, next) => {
    	  // req和res与之前的所有req和res是一样的
    	  // next() 调用时,可以让请求继续触发后续的操作(中间件、请求处理)
    	  //	- 如果没有调用next,请求就不会继续向后执行
    	  //		- app.use(express.static(...)); 内部没有设置next,后续的请求处理就没有意义了
    	});
    
    • 指定接口地址进行中间件设置
app.use('/common/post指定的接口名', (req, res, next) => {
  // 只有这个指定的接口会经过这个中间件的处理
  //	- app.use('地址', express.static(...));
});
* 自定义的内部处理
	* 可以自己在app.use的回调函数中进行条件判断
	* 通过条件的执行中间件功能
	* 不通过的直接设置next()

文件上传 multer 模块

  • 使用了multer中间件
    • 安装:npm i multer
    • 使用步骤:
      • 引入
      • 配置multer相关信息
        • multer({ dest: '地址' }); // 简略处理方式
        • 详细配置,上传地址,上传文件名等
      	let upload = multer({
      	  storage: multer.diskStorage({
      
      	    destination: (req, file, callback) => {
      	      callback(null, '绝对路径形式的地址');
      	      // callback的参数
      	      // - 参数1:是错误信息,如果设置了,就没法正常保存了,写null即可
      	      // - 参数2:是保存的位置,绝对路径
      	    },
      	    filename: (req, file, callback) => {
      	      callback(null, '文件名称');
      	      // callback的参数
      	      // - 参数1:是错误信息,如果设置了,就没法正常保存了,写null即可
      	      // - 参数2:是保存的位置,绝对路径
      	    	//	- 可以使用string-random进行随机文件名称的生成
      	    },
      	  })
      	});
      
      	// 在接口中调用upload.single('文件域的name值')
      	app.post('接口地址', upload.single('name值'), (req, res) => {
      	  // req.file用来查看上传后的文件的信息
      	  // console.log(req.file);
      	  res.send('ok');
      	});
      
  • string-random 模块
    • 下载并引入: const sr = require('string-random')
    • 使用 sr() 或 sr(随机字符串长度),默认 8 位

express中的路由管理方式

编程中的路由指的是 对 ‘客户端到服务端的请求’ 进行处理的一种方式
例如:客户端通过 /common/post 发送请求,服务端对 /common/post进行处理,这就是设置了一个路由操作。(其实就是设置了一个接口,一个接口就可以称为是一个路由)

  • 基本的路由设置方式:
    • app.get(地址,回调);
    • app.post(地址,回调);
  • express 中的路由管理:方便模块化编程,减少代码都在一个文件的问题
    • 使用方式:
      • 将某部分路由功能放到一个单独的模块中保存
        • 通过let router = express.Router()创建一个路由管理器实例
          • router的使用方式与app相同
        • 通过 module.exports = router 方式进行导出/暴露
      • 从server.js中进行模块的引入
        • 通过app.use()以中间件方式使用路由模块的功能
      	let uesrRouter = require('./router/userRouter.js');
      	app.use('/user', userRouter);
      
      • 注意:userRouter模块内部设置具体路由时,就不要再写 /user的部分了

cookie和session

http是一种无状态的协议:
每次客户端请求服务端时,都是'初次见面'

cookie

  • cookie在登录中的设置方式:
    • 问题:服务端每次接受用户的请求时,用于http无状态,所以无法确定用户的身份
    • 为了解决这个无状态的问题,会在登录成功时,服务端给用户下发cookie的数据(小票)
    • cookie的数据是保存在客户端的
    • 等以后用户再请求服务端时,带着小票一起发送过去(自动),服务端检测小票的信息即可判断这个用户使用访问过
  • 操作方式:
    • 设置方式:
      • res.cookie();
        • 参数1:cookie 数值的名称
        • 参数2:cookie 数值的值
        • 参数3:配置信息,对象
          • expires 过期时间:值为 new Date(Date.now() + 1000 * 60 * 60 * 24 * 7)
    • 获取方式:
      • req.headers.cookie 原生写法,获取的是字符串结构不好用
      • 使用中间件cookie-parser
        • 安装并引入并调用
          • let cp = require('cookie-parser');
          • app.use(cp());
        • 这个中间件给req设置了cookies属性,可以访问对象形式的cookie数据
    • 清除方式
      • res.clearCookie('cookie数据的名称');
  • express中的重定向方式
    • res.redirect('重定向到哪个地址');
      • 注意:设置重定向后不用再进行res.send()操作

session

session不是一个新技术,实际上就是cookie的一种使用方式

  • session实现方式:
    • 因为cookie是保存在客户端的,用户可以随意修改或伪造
    • 服务端不再将数据直接下发到客户端保存了,而是将数据保存在服务端
    • 下发的是保存数据区域的标识(手牌)
    • 用户下次请求时带着标识到服务端,开箱子读取数据进行操作即可
  • 好处:确保数据无法被用户操作,安全
  • 设置方式
    • 使用了express-session中间件
    • 下载并引入:let es = require('express-session');
    • 引入中间件并进行设置:app.use(es({secret: 'helloworld'}))
    • 读写操作
      • req.session 是一个对象,可以进行读写操作
      • req.session.destroy() 清除session数据
    • 注意:服务端清除了session数据后,客户端存储的session标记不会被清除,也没用了
// 设置 session 会话处理
app.use(es({
    secret: 'test',
    cookie: {
        // 1 分钟后失效
        maxAge: 1000 * 60,
    }
}))

app.use((req, res, next) => {
    // 刷新 session 的有效期
    req.session._garbage = Date();
    req.session.touch();
    next();
})

跨域

同源策略

同源策略是浏览器自带的一种安全机制。
本质上就是浏览器不允许我们使用ajax进行非同源地址的访问

  • 同源地址:
    • 指的是三个部分相同的地址
      • 协议
      • 域名
      • 端口
    • 但是因为现在的网站功能非常丰富,经常出现一个公司的网站有很多域名的情况
      • 这时就需要我们能够突破浏览器的同源策略限制,这种操作称为 跨域

CORS 跨域方式(IE10以上)

* cross origin resource sharing 跨域资源共享
// 如果我们希望这个接口可以被任意的其他非同源地址用ajax请求,可以设置一个响应头
  //  - 设置为*表示随便访问
  // res.header('Access-Control-Allow-Origin', '*');
  //  - 设置为指定地址可以让指定地址访问
  // res.header('Access-Control-Allow-Origin', 'http://localhost:5000');
  //  - 如果希望设置多个源都可以访问,一定不能覆盖设置
  //    - 1 通过req.headers.origin接收当前源的地址
  //    - 2 自己进行检测

  let arr = ['http://localhost:5000', 'http://localhost:6666', 'http://localhost:7777'];
  let index = arr.indexOf(req.headers.origin); // arr.includes()

  if (index !== -1) {
    res.header('Access-Control-Allow-Origin', req.headers.origin)
  }

JSONP

  • 是一种传统的跨域解决方案
    • JSONP不是规范中提出了跨域方式,而是开发者通过实践得到的一种解决方法
      • 随着规范的不断完善,推出了CORS的跨域解决方案
  • 使用场景:
    • CORS公司内部进行使用
    • JSONP会在我们请求第三方接口时常用
  • 什么是JSONP
    • 因为浏览器的同源策略限制了ajax的
      请求,JSONP就不采用ajax进行请求操作了
    • json with padding 常用的数据格式为JSON,所以JSONP中通常也传递的是JSON,所以起名叫JSONP
  • 设置方式:
    • 原生的设置方式:
      • 客户端的处理方式
        • 设置script标签,将src设置为接口地址
        • 通过callback参数传递客户端的处理函数名称
        • 在这个请求发送前设置好对应的处理函数即可
      • 服务端的处理方式:
        • 服务端接收处理函数名称
        • 响应函数调用形式的字符串,并传入数据即可
    • jQuery的设置方式:
      • 在jQuery的ajax方法中设置一个dataType属性,值为'jsonp'
    • axios不支持JSONP
  • JSONP的特点:
    • 没有兼容性问题
    • 只能发送get请求

数据库

基本语句

  • 查询语句
1 注释写法

  -- 这里是注释的内容

  2 sql语句的使用

  2.1 select语句 - 用来进行数据的查询操作(获取数据库中的数据)
  -- select 字段名 from 表名
  select * from user 获取user表中的所有数据
  select id from user 获取某个字段的数据
  select id,username,userage from user 获取多个字段的数据

  -- where子句的使用
  select * from user where id=1   指定条件
  select * from user where id in (1,2)  指定某个字段的多个情况
  select * from user where id>1 and userage=22  指定多个条件
  select * from user where id>1 or userage=18  多个条件满足某个

  -- order by子句的使用
  select * from user order by 字段名  根据指定字段排序,默认升序
  select * from user order by userage

  select * from user order by 字段名 desc 根据指定字段排序,降序
  select * from user order by userage desc

  -- limit 限制
  select * from user limit 2 只要最前面2条

  select * from user limit 0,3 后面的第一个值就像slice的参数一样,索引值, 第二个是个数

  - 分页数据的读取方式: 如果每页获取3条 (了解)
  select * from user limit 0,3  第一页
  select * from user limit 3,3  第二页
  select * from user limit 6,3  第三页
  select * from user limit 9,3  第四页
  select * from user limit (page-1) * 3, 3 第page页
  • 增删改
    • insert into
    	 2.2 insert into 用来进行数据的新增操作(下面的多种操作方式,随意掌握一个即可)
    
    	  insert into 表名 (字段名) values(数据...)
    
    	  insert into user (userage,username) values(17, 'jack')  指定字段设置值时,必须与名称顺序对应
    
    	  insert into user values(null,'吴悠',13) 不指定字段,设置值时必须按照表格顺序设置,不填的写null
    
    	  下面是同时设置多条的书写方式:
    	  insert into user values(null,'吴悠1',12),(null,'吴悠2',11),(null,'吴悠3',10)
    	  insert into user (userage,username) values(127, 'jack1'),(137, 'jack2'),(147, 'jack3')
    
    	  insert into user set username='rose2', userage=19
    
    • delete 和 update
2.3 delete 用来进行数据删除
  delete from user; 删除user表中所有数据,不要轻易尝试

  通常delete都与where子句结合使用 (之前使用的where操作与这里是一样的)
  delete from user where id=12 指定条件删除数据
  delete from user where id in (2,5,14)


  2.4 update 更新数据(修改)
  update 表名 set 字段名=值;  更新指定表中的所有数据,不要轻易尝试

  通常update都与where结合使用
  update user set username='abc' where id=3
  update user set username='xyz',userage=36 where id in (3,4)

通过nodejs操作数据库的方式

  • 使用一个mysql的包进行数据库操作,安装的这个mysql包,是让nodejs方便的操作mysql数据库使用的
  • 安装方式: npm install mysql
  • 基本使用方式:
    • 引入: const mysql = require('mysql');
    • 创建连接对象:
    	const con = mysql.createConnection({
    	  host: 'localhost',	// 主机地址,主机名
    	  user: 'root',		// 用户名
    	  password: 'root',	// 密码
    	  port: '3306',		// 端口号
    	  database: 'demo'	// 数据库名
    	});
    
    • con.query()
      • 参数1必选,sql语句
      • 参数2可选:占位符的数据(可以选择性记忆)
      • 参数3必选:回调函数
        • 参数1 err错误信息
        • 参数2 result
          • 如果是select操作,result是获取到的数据,数组格式
          • 如果是非查询操作,result是操作结果的对象形式的信息
            • affectedRows 代表受影响的行数,应当进行检测
  • 数据库对比json文件的好处
    • json文件可以随便操作,数据库有用户名和密码,更安全一些
    • json文件需要自己书写js代码进行数据操作,数据库有sql语句,操作更方便简洁

异步操作管理

为什么要进行异步操作的管理

  1. 异步操作都会有回调函数
  2. 回调一层一层嵌套的写法用一个特殊的称呼:回调地狱
  3. 会出现各种的嵌套操作
  • 嵌套写法:
    	const fs = require('fs');
    	// 需求:按顺序依次读取3个json文件,并输出内容
    	// 1 第一次读取
    	fs.readFile('./1.json', 'utf-8', (err, data) => {
    	  if (err) { throw err; }
    	  console.log(data); // 输出文件内容
    	  // 2 第二个文件读取
    	  fs.readFile('./2.json', 'utf-8', (err, data) => {
    	    if (err) { throw err; }
    	    console.log(data); // 输出文件内容
    	    // 3 第三个文件读取
    	    fs.readFile('./3.json', 'utf-8', (err, data) => {
    	      if (err) { throw err; }
    	      console.log(data); // 输出文件内容
    	    });
    	  });
    	});
    

promise

目的:通过promise的处理,让异步操作书写的像同步代码一样,不需要书写多层的嵌套结构

  • 使用方式
    • new Promise()
      • 参数为回调函数
        • 参数1是用来触发then()的函数
        • 参数2是用来触发catch()的函数
    • promise实例对象有3个方法
      • then() catch() finally()
  • 解决上面的示例问题
    • 好处:可以让多次异步操作不需要书写为嵌套结构
    • 书写形式为链式写法(同步写法)
// 将读取文件功能进行封装
function readFile(path) {
  // 将当前读取文件的promise对象返回
  return new Promise((ok, err) => {
    // 在promise中进行异步操作设置
    fs.readFile(path, 'utf-8', (error, data) => {
      // 如果有错误产生,触发catch
      if (error) {
        // 调用err()
        err('读取文件错误');
      } else {
        // 成功时调用ok()触发then()
        ok(data);
      }
    })
  });
}

// 进行多次异步的文件读取操作
//  - 当给then()设置的返回值是promise对象,这个对象会成为then的返回值
readFile('./1.json')
  .then((data) => {
    // 第一次文件读取
    console.log(data);  // 第一次输出

    // 希望读取第二次文件
    return readFile('./21.json')
  })
  .then((data) => {
    console.log(data); // 第二次输出

    // 希望读取第三次文件
    return readFile('./3.json');
  })
  .then((data) => {
    console.log(data);  // 第三次输出
  })
  .catch((err) => {
    console.log(err);
  });

async await

ES6认为promise的.then() .catch()还是有些麻烦,又推出了一种新的写法,用来简化

// 使用async和await时,只需要设置ok的调用即可
function readFile(path) {
  return new Promise((ok, err) => {
    fs.readFile(path, 'utf-8', (error, data) => {
      // 无论成功还是失败都触发ok
      if (error) {
        ok(null); // 失败时传入一些错误信息
      } else {
        ok(data); // 成功时传入数据
      }
    })
  });
}


// --- 下面的是常用的写法:
// 1 后续我们设置一个async函数
async function myFun() {
  // 2 调用可以返回promise对象的函数readFile()
  //  - 调用前设置await
  let data1 = await readFile('./1.json');
  // console.log(data1); // 可以根据data1的结果进行ifelse判断

  let data2 = await readFile('./2.json');
  console.log(data2);

  let data3 = await readFile('./3.json');
  console.log(data3);
}
myFun();
内事不懂问百度,外事不懂问谷歌~