服务端开发

服务端开发概述

  • 很久之前,网站是静态网站(网页都是提前写好的)
    • 缺点:后期维护不方便;无法做交互。
    • Web2.0:网站需要有交互,互联网中的内容开始由用户产生。
  • 动态网站(PHP、Java、python、node.js。。。。。。)
    • 网页是有后端动态生成的这种网站(服务端渲染)
    • 比如访问这个链接http://abc.com/asdfa.php,那么返回的结果是HTML页面
    • 服务端渲染:把数据动态填充到页面模板中的过程从而生成静态的HTML页面
  • 早期主要是服务端渲染(客户端发送请求给服务器,服务器返回渲染好的HTML页面,而不是数据)
  • 服务端渲染有性能问题,浏览器加载页面的页面过程是同步的(阻塞的,体验不好)
  • 所以后来就诞生了ajax,那么服务端渲染和Ajax会结合使用(在这种模式之下,后端既要做服务端渲染,也要做接口,给Ajax调用)
  • 前后端分离:代码分开,开发分开,人员分开—>前端一个项目(把接口数据动态填充到HTML标签里:前端渲染);后端一个项目(专门做接口)
  • 现在主流模式是客户端渲染(客户端通过Ajax调用后端接口,后端接口返回JSON数据,客户端把数据渲染出来)

现在主流开发模式:前后端分离(前后端项目时分开的,各自开发各自的项目)

  • 网站开发方式(把数据拼接到标签中的过程称之为渲染)
    • 服务端渲染(渲染的过程发生在后端)
    • 客户端渲染(渲染的过程发生在前端)
      • 如果是客户端渲染的话,后端主要职责是向前端提供接口数据(我们要体会后端做接口的过程)

express介绍

基于 Node.js 平台,快速、开放、极简的 Web 开发框架

总结:基于express做后台接口更容易

Express基本用法

  • 创建项目文件夹
  • 初始化项目
    • npm init -y
  • 安装依赖包
  1. npm i express
  • 使用Express构建Web服务器步骤:

      1. 加载 express 包

      1. 创建 express 服务器实例对象

      1. 开启服务器(监听端口)

      1. 监听浏览器请求并进行处理
  1. /*
  2. 基于express框架实现后端接口开发
  3. */
  4. // 1、导入express包
  5. const express = require('express')
  6. // 2、创建服务端实例对象
  7. const app = express()
  8. // 3、监听服务器端口
  9. app.listen(3000, () => {
  10. console.log('running...')
  11. })
  12. // 4、处理客户端请求
  13. app.get('/data', (req, res) => {
  14. res.send('hello world!')
  15. })

总结:

  1. 基于包的结构创建项目
  2. 安装express依赖包
  3. 基于express实现接口功能

Express后端路由

后端路由概念分析

  • 路由:实现分发功能
  • 路由器:分发消息
  • 后端路由:分发客户端的所有请求(根据不同的请求返回不同的数据)
    • 分发客户端所有请求

image.png

总结:后端路由用于分发客户端的所有请求,根据不同的请求分发到不同的处理函数中,从而得到数据并返回。

  • 后端路由代码
  1. // 4、监听客户端请求
  2. app.get('/data', (req, res) => {
  3. res.end('hello')
  4. })
  5. app.get('/home', (req, res) => {
  6. res.setHeader('Content-Type', 'text/html; charset=utf-8')
  7. res.end('你好')
  8. })

总结:express提供了大量的可以直接使用的路由方法,用来监听客户端不同的接口请求。

实现静态资源服务

  • 把页面相关内容以网站方式(http方式)去访问
  1. // 静态资源服务(public目录中的所有资源都可以通过url访问)
  2. app.use(express.static('public'))
  • 启动静态资源服务后,那么静态资源和后端接口请求地址就不跨域了,从而方便用ajax测试接口
  1. <div>测试Ajax</div>
  2. <hr>
  3. <button id="btn">点击</button>
  4. <script src="https://cdn.bootcdn.net/ajax/libs/axios/0.21.1/axios.min.js"></script>
  5. <script>
  6. const btn = document.getElementById('btn')
  7. btn.onclick = function () {
  8. axios.get('http://localhost:3000/data').then(function (ret) {
  9. console.log(ret)
  10. })
  11. }
  12. </script>

总结:后端的页面和接口在同一个项目中,所以不跨域

解决后端代码频繁重启问题

  • 需要安装一个包nodemon
  1. npm i nodemon -g
  • 使用nodemon命令启动node程序
  1. nodemon index.js

总结:使用nodemon命令启动的程序,修改代码后不需要手动重启

后端路由相关方法用法

  • 增删改查
    • app.get 查询
    • app.post 添加
    • app.put 修改
    • app.delete 删除
  1. app.get('/data', (req, res) => {
  2. res.send('hello get!')
  3. })
  4. app.post('/data', (req, res) => {
  5. res.send('hello post!')
  6. })
  7. app.put('/data', (req, res) => {
  8. res.send('hello put!')
  9. })
  10. app.delete('/data', (req, res) => {
  11. res.send('hello delete!')
  12. })

get方法

  • get方法
  1. // app.get('请求的URL', callback);
  2. app.get('/api/data', (req, res) => {
  3. // 处理GET方式的/api/getbooks接口
  4. });
  • 获取get参数

请求地址的写法: http://localhost:3006/test?id=3&bookname=zxx&age=20

  1. // 写接口
  2. app.get('/test', (req, res) => {
  3. console.log(req.query); // { id: '3', bookname: 'zxx', age: '20' }
  4. });

post方法

POST请求体,有哪些格式

  • 查询字符串(bookname=zzz&author=yyy&publisher=zzz) — 对应的Content-Type: application/x-www-form-urlencoded
  • 请求参数是json格式-对应得Content-Type: application/json
  1. // 1、导入express包
  2. const express = require('express')
  3. // 2、创建服务端实例对象
  4. const app = express()
  5. // 3、监听服务器端口
  6. app.listen(3000, () => {
  7. console.log('running...')
  8. })
  9. // 前端请求post参数的类型
  10. // content-type application/x-www-form-urlencoded
  11. // uname=zhangsan&age=12
  12. // content-type application/json
  13. // {"uname":"zhangsan","age":12}
  14. // 处理请求参数
  15. app.use(express.urlencoded({ extended: true })) // for parsing application/x-www-form-urlencoded
  16. app.use(express.json()) // for parsing application/json
  17. // 4、处理客户端请求(设置了一个接口)
  18. // 后端路由:所有的处理客户端请求地址的这些方法的统称
  19. app.post('/data', (req, res) => {
  20. let param = req.body
  21. console.log(param)
  22. res.json(param)
  23. // res.json({
  24. // status: 0,
  25. // message: '测试成功'
  26. // })
  27. })

post传参问题

  • 测试两种post传参方式
    • 前端可以发送www格式的请求参数
    • 也可以发送json格式的请求参数
    • 两种参数类型后端都需要进行相应设置
  1. // axios请求传递的参数默认就是json格式
  2. // Content-Type: application/json
  3. // {"bookname":"水浒传","author":"施耐庵","publisher":"出版社"}
  4. axios.post('http://localhost:3000/data', {
  5. bookname: '水浒传',
  6. author: '施耐庵',
  7. publisher: '出版社'
  8. }).then((res) => {
  9. console.log(res)
  10. })
  11. // const params = new URLSearchParams()
  12. // params.append('bookname', '西游记')
  13. // params.append('author', '吴承恩')
  14. // Content-Type: application/x-www-form-urlencoded
  15. // bookname=%E8%A5%BF%E6%B8%B8%E8%AE%B0&author=%E5%90%B4%E6%89%BF%E6%81%A9
  16. // axios.post('http://localhost:3000/data', params)
  17. // .then(function (res) {
  18. // console.log(res)
  19. // })

Restful参数获取

请求地址的写法: http://localhost:3000/api/books GET http://localhost:3000/api/books POST http://localhost:3000/api/books/123 DELETE http://localhost:3000/api/books/123 GET http://localhost:3000/api/books/123/comments/456

  1. /* 基于express框架实现后端接口开发 获取Restful形式的参数*/// 1、导入express包const express = require('express')// 2、创建服务端实例对象const app = express()// 3、监听服务器端口app.listen(3000, () => { console.log('running...')})// 4、处理客户端请求(设置了一个接口)app.get('/books/:bid', (req, res) => { // params名称是固定的吗?是的 let param = req.params res.json(param)})app.get('/books/:bid/comment/:cid', (req, res) => { // params名称是固定的吗?是的 let param = req.params param.abc = 'hello' res.json(param)})
  • 前端发送请求需要向后端提交参数
    • 前端发送请求参数有几种方式?
      1、参数以问号方式拼接到url地址中 ?id=123&name=lisi
      2、以请求体方式发送请求参数
      applicaiton/x-www.form-urlencoded
      application/json
      3、restful风格的请求参数(也在地址中) /books/123/comments/456
    • 后端如何得到上述参数?
      1、req.query
      2、req.body
      3、req.params
    • get 请求用查询字符串传递参数
    • post/put/delete都可以用请求体传递数据

关于重启问题

  • 为了防止修改代码后频繁重启的问题,可以使用nodemon解决
    • 先全局安装包 npm i nodemon -g
    • 再使用nodemon运行代码 nodemon index.js
  • 这样启动的项目,只要修改代码并保存,那么会自动重启

回顾

  • Node.js作为工具使用(npm用法)
    • 模块化开发:提供开发效率(方便重用);方便后期维护和扩展(模块有隔离性,彼此之间影响比较小)
    • 需要有一套标准规范来具体规定模块化的细节:AMD/CMD/CommonJS/ESM
    • CommonJS的核心规则
      • 模块成员的导出:module.exports = {}
      • 导入其他模块的成员:require(参数)
        • 核心模块的名称
        • 自定义模块的路径
        • 第三方包名称
      • 包可以包含更多模块,从而实现更加复杂的功能,进而方便更大规模的代码重用。
      • npm这个网站可以托管所有的npm包,用于方便大家共享
      • npm也是一个工具,用于进行下载安装、更新、卸载、发布包等操作
      • 安装包的方式:全局安装(作为命令使用);局部安装(开发使用)
      • 如何自己做一个规范的包
        • 创建一个目录作为一个包(目录的名称必须全部小写)
        • 包的根目录中必须有一个package.json文件,用于描述这个包
        • 一般需要提供一个文档readme.md用于描述这个包的功能
        • 至少需要一个包的入口文件,通过package.json文件的main属性指向这个入口文件
      • package.json的相关属性的作用
        • name和version 用于唯一区分是哪个包
        • main 用于指定包的入口
        • scripts 用于给执行的命令起一个别名
        • dependencies 生产依赖
        • devDependencies 开发依赖
      • 下载包比较慢(默认从国外下载)
        • 设置淘宝镜像
          • npm config set registry 淘宝镜像地址
          • 基于nrm包管理镜像
      • 如何发布正式的包
        • 注册npm账号(验证邮箱)
        • 命令行登录npm官网:npm login
        • 执行发布的动作 npm publish
        • 取消发布 npm unpublish 包名 —force
  • Node.js开发后端接口
    • 网站开发模式,现在的主流是前后端分离(前端独立开发;后端独立开发)
      • 前端主要负责页面效果,以及与后端的接口对接
      • 后端主要负责做接口(处理数据库操作)
    • 基于Node.js提供的核心模块可以实现接口服务,但是很麻烦
    • 需要基于express框架实现接口开发(他封装了底层的核心模块http)
    • express实现接口的基本步骤
      • 安装express包
      • 导出express
      • 创建一个服务器对象
      • 监听端口
      • 监听不同的接口路径
    • 理解后端路由的概念(分发客户端的所有请求)
    • 启动静态资源服务,方便测试Ajax调用接口(此时网页和接口在同一个项目中,不跨域)
    • 默认情况,修改后端代码,需要重启服务,为了不频繁重启,可以基于nodemon运行代码

Express中间件

中间件概念分析

  • 图中,自来水厂从获取水源到净化处理交给用户,中间经历了一系列的处理环节,我们称其中的每一个处理环节就是一个中间件(对经过的水进行处理)
  • 客户端发送请求 —->中间件1(对经过的数据进行处理) —-> 中间件2 —-> …… —-> 服务器端接口
  • 中间件分类
    • 内置中间件:静态资源服务中间件;post请求参数处理中间件
    • 自定义中间件:自己写代码实现一个中间
    • 第三方中间件:需要先安装再使用 cors
    • 。。。。。。
  • 中间件的本质其实就是一个函数

内置中间件基本用法

  • 静态资源中间件
  1. app.use(express.static('public'))
  • post请求数据处理中间件
  1. app.use(express.urlencoded({extended: false}))
  2. app.use(express.json())

总结:

  1. 内置中间需要使用app.use方法使用
  2. 静态资源服务中间件
  3. 处理post请求参数(www格式和json格式处理)

Express自定义中间件

  • 中间件本质上就是一个函数
  • 中间件函数中有四个基本参数, err、req、res、next
    • 很多情况下,err都会省略
    • req就是请求相关的对象
    • res就是响应相关的对象
    • next:它是一个函数,某些时候,可以省略
  • 把写好的中间件函数,传递给 app.use()使用
  1. // 自定义一个中间件app.use(function (req, res, next) { // 向req对象中添加了一个属性salt req.salt = '盐' // 把请求参数变成大写 req.query.info = req.query.info && req.query.info.toUpperCase() // 当前中间件处理请求完成后,需要调用next方法进入下一个中间件进行处理 next()})

总结:

  1. 中间件本质上就是函数
  2. 要想进入下一个环节,需要手动调用next方法
  3. 如何获取get请求参数?req.query
  4. 如何获取post请求参数?req.body
  • 错误处理中间件
  1. // 统一错误处理中间件
  2. app.use(function (err, req, res, next) {
  3. // err.stack表示详细的错误信息
  4. console.error(err.stack)
  5. // res.status用于设置一个http响应状态吗
  6. res.status(500).send('服务端发生错误!')
  7. })

总结:错误处理中间件一般放到最后,前面任何一个环节出错了,那么立刻终止并直接进入错误处理中间件,在这里可以定制返回的状态吗和错误消息,从而给一个友好的统一的返回格式。

CORS解决跨域

  • 使用第三方npm包中间件实现跨域资源共享CORS

    • 安装依赖包

      1. npm i cors
    • 导入中间件

      1. const cors = require('cors')
    • 使用中间件

      1. app.use(cors())
  • 本质上是设置响应头

  1. // app.all表示监听所有请求方式,*表示所有路径都匹配
  2. app.all('*', function (req, res, next) {
  3. // 设置响应头
  4. // 设置运行访问的域名
  5. res.header('Access-Control-Allow-Origin', '*')
  6. // 设置运行访问的请求头
  7. res.header('Access-Control-Allow-Headers', 'Content-Type')
  8. // 设置运行的请求方式
  9. res.header('Access-Control-Allow-Methods', '*')
  10. next()
  11. })

总结:通过后端的cors方案解决跨域,cors是一个第三方包,本质上是中间件,cors底层本质上是设置响应头(允许的请求域名、请求头和请求方式)

接口返回json数据

  • 通过res.send和res.json都可以直接返回json字符串格式的数据
  • res.json的好处:可以设置响应头Content-Type: application/json; charset=utf-8
  1. app.get('/books', (req, res) => {
  2. console.log(bookData)
  3. // res.send(bookData)
  4. // res.json(bookData)
  5. // res.json方法会自动把对象转换为字符串
  6. res.json({
  7. uname: 'hello'
  8. })
  9. })

总结:res.json可以直接把对象数据转换为json字符串然后返回给前端并且设置json响应头。