服务端开发
服务端开发概述
- 很久之前,网站是静态网站(网页都是提前写好的)
- 缺点:后期维护不方便;无法做交互。
- 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
- 安装依赖包
npm i express
- 使用Express构建Web服务器步骤:
- 加载 express 包
- 创建 express 服务器实例对象
- 开启服务器(监听端口)
- 监听浏览器请求并进行处理
/*
基于express框架实现后端接口开发
*/
// 1、导入express包
const express = require('express')
// 2、创建服务端实例对象
const app = express()
// 3、监听服务器端口
app.listen(3000, () => {
console.log('running...')
})
// 4、处理客户端请求
app.get('/data', (req, res) => {
res.send('hello world!')
})
总结:
- 基于包的结构创建项目
- 安装express依赖包
- 基于express实现接口功能
Express后端路由
后端路由概念分析
- 路由:实现分发功能
- 路由器:分发消息
- 后端路由:分发客户端的所有请求(根据不同的请求返回不同的数据)
- 分发客户端所有请求
总结:后端路由用于分发客户端的所有请求,根据不同的请求分发到不同的处理函数中,从而得到数据并返回。
- 后端路由代码
// 4、监听客户端请求
app.get('/data', (req, res) => {
res.end('hello')
})
app.get('/home', (req, res) => {
res.setHeader('Content-Type', 'text/html; charset=utf-8')
res.end('你好')
})
总结:express提供了大量的可以直接使用的路由方法,用来监听客户端不同的接口请求。
实现静态资源服务
- 把页面相关内容以网站方式(http方式)去访问
// 静态资源服务(public目录中的所有资源都可以通过url访问)
app.use(express.static('public'))
- 启动静态资源服务后,那么静态资源和后端接口请求地址就不跨域了,从而方便用ajax测试接口
<div>测试Ajax</div>
<hr>
<button id="btn">点击</button>
<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.21.1/axios.min.js"></script>
<script>
const btn = document.getElementById('btn')
btn.onclick = function () {
axios.get('http://localhost:3000/data').then(function (ret) {
console.log(ret)
})
}
</script>
总结:后端的页面和接口在同一个项目中,所以不跨域
解决后端代码频繁重启问题
- 需要安装一个包nodemon
npm i nodemon -g
- 使用nodemon命令启动node程序
nodemon index.js
总结:使用nodemon命令启动的程序,修改代码后不需要手动重启
后端路由相关方法用法
- 增删改查
- app.get 查询
- app.post 添加
- app.put 修改
- app.delete 删除
app.get('/data', (req, res) => {
res.send('hello get!')
})
app.post('/data', (req, res) => {
res.send('hello post!')
})
app.put('/data', (req, res) => {
res.send('hello put!')
})
app.delete('/data', (req, res) => {
res.send('hello delete!')
})
get方法
- get方法
// app.get('请求的URL', callback);
app.get('/api/data', (req, res) => {
// 处理GET方式的/api/getbooks接口
});
- 获取get参数
请求地址的写法: http://localhost:3006/test?id=3&bookname=zxx&age=20
// 写接口
app.get('/test', (req, res) => {
console.log(req.query); // { id: '3', bookname: 'zxx', age: '20' }
});
post方法
POST请求体,有哪些格式
- 查询字符串(bookname=zzz&author=yyy&publisher=zzz) — 对应的
Content-Type: application/x-www-form-urlencoded
- 请求参数是json格式-对应得Content-Type: application/json
// 1、导入express包
const express = require('express')
// 2、创建服务端实例对象
const app = express()
// 3、监听服务器端口
app.listen(3000, () => {
console.log('running...')
})
// 前端请求post参数的类型
// content-type application/x-www-form-urlencoded
// uname=zhangsan&age=12
// content-type application/json
// {"uname":"zhangsan","age":12}
// 处理请求参数
app.use(express.urlencoded({ extended: true })) // for parsing application/x-www-form-urlencoded
app.use(express.json()) // for parsing application/json
// 4、处理客户端请求(设置了一个接口)
// 后端路由:所有的处理客户端请求地址的这些方法的统称
app.post('/data', (req, res) => {
let param = req.body
console.log(param)
res.json(param)
// res.json({
// status: 0,
// message: '测试成功'
// })
})
post传参问题
- 测试两种post传参方式
- 前端可以发送www格式的请求参数
- 也可以发送json格式的请求参数
- 两种参数类型后端都需要进行相应设置
// axios请求传递的参数默认就是json格式
// Content-Type: application/json
// {"bookname":"水浒传","author":"施耐庵","publisher":"出版社"}
axios.post('http://localhost:3000/data', {
bookname: '水浒传',
author: '施耐庵',
publisher: '出版社'
}).then((res) => {
console.log(res)
})
// const params = new URLSearchParams()
// params.append('bookname', '西游记')
// params.append('author', '吴承恩')
// Content-Type: application/x-www-form-urlencoded
// bookname=%E8%A5%BF%E6%B8%B8%E8%AE%B0&author=%E5%90%B4%E6%89%BF%E6%81%A9
// axios.post('http://localhost:3000/data', params)
// .then(function (res) {
// console.log(res)
// })
Restful参数获取
- Restful:url地址的一种风格
- http://localhost:3000/getbooks?id=123
请求地址的写法: 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
/* 基于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
- 。。。。。。
- 中间件的本质其实就是一个函数
内置中间件基本用法
- 静态资源中间件
app.use(express.static('public'))
- post请求数据处理中间件
app.use(express.urlencoded({extended: false}))
app.use(express.json())
总结:
- 内置中间需要使用app.use方法使用
- 静态资源服务中间件
- 处理post请求参数(www格式和json格式处理)
Express自定义中间件
- 中间件本质上就是一个函数
- 中间件函数中有四个基本参数, err、req、res、next
- 很多情况下,err都会省略
- req就是请求相关的对象
- res就是响应相关的对象
- next:它是一个函数,某些时候,可以省略
- 把写好的中间件函数,传递给
app.use()
使用
// 自定义一个中间件app.use(function (req, res, next) { // 向req对象中添加了一个属性salt req.salt = '盐' // 把请求参数变成大写 req.query.info = req.query.info && req.query.info.toUpperCase() // 当前中间件处理请求完成后,需要调用next方法进入下一个中间件进行处理 next()})
总结:
- 中间件本质上就是函数
- 要想进入下一个环节,需要手动调用next方法
- 如何获取get请求参数?req.query
- 如何获取post请求参数?req.body
- 错误处理中间件
// 统一错误处理中间件
app.use(function (err, req, res, next) {
// err.stack表示详细的错误信息
console.error(err.stack)
// res.status用于设置一个http响应状态吗
res.status(500).send('服务端发生错误!')
})
总结:错误处理中间件一般放到最后,前面任何一个环节出错了,那么立刻终止并直接进入错误处理中间件,在这里可以定制返回的状态吗和错误消息,从而给一个友好的统一的返回格式。
CORS解决跨域
使用第三方npm包中间件实现跨域资源共享CORS
安装依赖包
npm i cors
导入中间件
const cors = require('cors')
使用中间件
app.use(cors())
本质上是设置响应头
// app.all表示监听所有请求方式,*表示所有路径都匹配
app.all('*', function (req, res, next) {
// 设置响应头
// 设置运行访问的域名
res.header('Access-Control-Allow-Origin', '*')
// 设置运行访问的请求头
res.header('Access-Control-Allow-Headers', 'Content-Type')
// 设置运行的请求方式
res.header('Access-Control-Allow-Methods', '*')
next()
})
总结:通过后端的cors方案解决跨域,cors是一个第三方包,本质上是中间件,cors底层本质上是设置响应头(允许的请求域名、请求头和请求方式)
接口返回json数据
- 通过res.send和res.json都可以直接返回json字符串格式的数据
- res.json的好处:可以设置响应头Content-Type: application/json; charset=utf-8
app.get('/books', (req, res) => {
console.log(bookData)
// res.send(bookData)
// res.json(bookData)
// res.json方法会自动把对象转换为字符串
res.json({
uname: 'hello'
})
})
总结:res.json可以直接把对象数据转换为json字符串然后返回给前端并且设置json响应头。