2025-03-29 14:35:49 +08:00

579 lines
13 KiB
Markdown
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Node开发框架——Express
注意项:
* 引入自定义模块要用相对路径, 如`./`
## 简介
官网: https://www.expressjs.com.cn/
> Express是一个Node.js Web 应用程序开发框架
> 最肤浅理解: 路由框架
核心特性:
* **路由**: 定义了路由表用于执行不同的 HTTP 请求动作。
* **中间件**: 本质是一个函数, 在收到请求和返回响应的过程中进行处理
* **模版引擎**: ......
## 类似的框架
fastify koa koa2
## 使用
```js
//初始化, 创建package.json文件
npm init -y
//安装express到项目
npm i express -S
```
```js
//引入Express框架
const express = require('express')
//创建一个web服务器
const app = express()
//给app对象注册一个中间器use是一个内置中间件可以用来监听get和post请求
app.use((req, res) => res.send('Hello World!'))
//开启服务器并监听端口
app.listen(3000, ()=>{
console.log('http://127.0.0.1:300')
})
```
## 使用express-generator生成项目骨架
......
## 中间件
中间件功能可以执行以下任务:
* 执行任何代码。
* 更改请求和响应对象。
* 结束请求-响应周期。
* 调用堆栈中的下一个中间件函数。
中间件的分类:
* 应用层中间件
* 路由器级中间件
* 错误处理中间件
* 内置中间件
* 第三方中间件
### 应用层中间件
使用`use()`或者`METHOD()`绑定函数到应用程序实例app上, METHOD 包括get和post
* ==中间件是有顺序的==
* next() 放行, 触发后续的中间件. 不放行, 后面的中间件都不执行.
* next() 不能在响应处理完毕后调用. 举例: res.send() 后不能再接 next()
```js
const app = express()
//每个请求都会执行
app.use(function (req, res, next) {
console.log('Time:', Date.now())
next()
})
//访问根目录时会执行
app.use('/', (req, res, next) {
console.log('Time:', Date.now())
next()
})
```
### 路由器中间件
使用`router.use()``router.METHOD()`函数加载路由器级中间件。
```js
// 创建路由对象
const router = express.Router()
router.get('/', function (req, res) {
res.send('hello, user!')
})
//安装路由
app.use(router)
```
### 错误处理中间件
```js
//错误处理中间件始终带有四个参数, 不能省略
app.use(function (err, req, res, next) {
console.error(err.stack)
res.status(500).send('Something broke!')
})
//要写在路由安装之后
```
### 内置中间件
Express具有以下内置的中间件功能
* express.static : 提供静态资产例如HTML文件图像等。
```js
// 中间件有顺序, 哪个在前, 哪个先生效
//实现静态页面的自动访问,它会默认打开views下面的index.html
app.use('/', express.static(path.join(__dirname, 'views')))
// 响应静态资源
app.use(express.static('public'))
```
* express.json: 。它使用JSON有效负载分析传入请求并基于body-parser
```js
app.use(bodyParser.json())
//接受一个对象或数组并在发送之前将其转换为JSON
res.json({ code: 204, msg: '注册失败' })
```
### 第三方中间件
通过使用第三方中间件从而为Express应用增加更加功能
安装所需要功能的node模块,并在应用中加载,可以在应用级加载,也可以在路由级加载。
* bodyParse: 处理程序之前,在中间件中对传入的请求体进行解析
```js
//先安装再使用
npm i body-parser -S
```
```js
const bodyPsrse = require('body-parser')
...
//bodyParser.json是用来解析json数据格式的
app.use(bodyParser.json())
//bodyParser.urlencoded则用来解析我们通常的form表单提交的数据
app.use(bodyParser.urlencoded({
extended: false
//设置为false采用querystring库如果设置为true那么采用qs库, 官方建议false
}))
//body-parse不支持FormData, 但post的其他数据都可以用它来接收
//get参数用express自带的req.query接收
```
## 路由
参数:
* 请求方式:
```js
app.get()
app.post()
app.put()
app.delete()
app.all()
```
* 请求路径: 只匹配对应路径的请求进行处理
```js
app.get('/', function (req, res) {
res.send('只响应根路由/')
})
app.get('/random.text', function (req, res) {
res.send('只响应/random.text')
})
```
* 处理方法:
```js
//单个回调函数
app.get('/example/a', function (req, res) {
res.send('Hello from A!')
})
//多个回调函数可以处理一条路由, 使用next()
app.get('/example/b', function (req, res, next) {
console.log('the response will be sent by the next function ...')
next()
}, function (req, res) {
res.send('Hello from B!')
})
```
## 项目
```
//1.规划项目文件夹
Demo
-data 数据
-node_modules 第三方模块
-public 公共资源
-css
-images
-js
-views 静态页面资源
-index.html 首页
-register
-app.js 程序入口
-handler.js 业务处理模块
-router.js 路由表
-package.json
-package-lock.json
//2. 引入内置模块
//3. 创建服务器, 监听端口
//4. 响应页面资源
//5. 静态资源托管
//6. 添加路由表 router.js
//7. 业务功能处理 handle.js
//8. 安装路由
```
### 程序入口 `app.js`
```js
const express = require('express')
const path = require('node:path')
const router = require('./router')
const bodyParser = require('body-parser')
let app = express()
app.listen(3002, () => {
console.log('127.0.0.1:3002')
})
// 添加body-parse解析器使用它进行处理post方式所传递的参数
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({
extended: false
}))
//static中间函数实现静态页面的自动访问
app.use('/', express.static(path.join(__dirname, 'views')))
//响应静态资源
app.use(express.static('public'))
//安装路由功能
app.use(router)
// 路由可以安装多个, 中间件都是有顺序的
// app.use('url1', router1) // 都在url1下, 路径为url1/xxx
// app.use('url2', router1)
```
### 路由表`router.js`
```js
// 引入express
const express = require('express')
const handler = require('./handler')
// 创建路由对象
const router = express.Router()
// 添加(挂载)路由配置
router.post('/register', (req, res) => {
handler.userRegister(req, res)
})
// 暴露
module.exports = router
```
### 业务处理 `handler.js`
```js
const fs = require('node:fs/promises')
const path = require('node:path')
// 专门进行业务处理
module.exports = {
userRegister: function (req, res) {
// express封装了get请求参数的接收
// req.query 是express自带的
// 接收用户参数 以前req.on('data') req.on('end') > 参数的类型转换
// body:就是post方式所传递的参数它是一个对象参数的接收和参数的转换
// console.log(req.body)
// 存储到指定的数据文件中,并响应结果
getRegister: (req,res) => {
console.log('req.body', req.body)
fs.readFile(path.resolve(__dirname, './data/users.json'),'utf-8')
.then(data=>{
let arr = JSON.parse(data)
arr.push(req.body)
return fs.writeFile(path.resolve(__dirname,'./data/users.json'),JSON.stringify(arr, null, ' '))
})
.then(result => {
res.json({code:200,msg:'注册成功'})
})
.catch(err=>{
console.log('err', err)
res.json({code:204,msg:'注册失败'})
})
}
}
```
* 重定向: res.redirect(path)
* 设置响应状态码: `res.status(404).send('404 not found')`
## 用户认证
另建笔记
## 模板引擎(了解)
服务端渲染:
| | 优势 | 劣势 |
| ------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
| 服务端 | 1. 前端耗时少<br>2. 有利SEO<br>3. 客户端速度更快, 耗电更少 |1. 不利于前后端分离, 开发效率低 <br/>2. 请求多时, 对服务器造成访问压力|
折中方案: "首屏渲染", 首屏加载用服务器渲染,其他的用客户端渲染
模板引擎有很多: [Template Engines](https://expressjs.com/en/resources/template-engines.html)
其中: 前台模板引擎 express-art-template [介绍 - art-template](https://aui.github.io/art-template/zh-cn/docs/)
**使用步骤:**
* 下载安装
* 先下载art-template:`npm i art-template -S`
* 再下载express-art-template: `npm i express-art-template -S`
* 给express框架设置默认的模板引擎
```js
// 注册模板引擎
app.engine('html', require('express-art-template')); //第一个参数是渲染文件的格式
app.set('view options', {
debug: process.env.NODE_ENV !== 'production' //设置模板引擎的使用环境, 开发和上线都要使用
});
```
* 设置模板引擎文件的默认路径
```js
// 如果不设置默认也是views
app.set('views',path.join(__dirname, './views')
app.set('view engine', 'html') //设置模板文件的类型
```
示例如下:
```js
// 1. 引入框架模块
var express = require('express')
// 2. 创建服务器
var app = express();
// 注册模板引擎
//engine就是个中间件
app.engine('html', require('express-art-template'));
app.set('view options', {
debug: process.env.NODE_ENV !== 'production'
});
// 如果不设置默认也是views
//app.set('views','./views')
// 3. 开启服务器并监听端口
app.listen(3000,()=>{
console.log('http://127.0.0.1:3000')
})
app.get('/login',function(req,res){
// 引入express-art-template之前会在Res上挂载一个render方法可以实现页面的渲染
// render实现的功能有 读取文件,动态渲染,响应, 所以不用fs去读文件
// res.render(要渲染的文件路径,[数据])
res.render('login.html', { list: [{ name: 'jack', age: 20 }, { name: 'rose', age: 18 }] })
})
```
`/views/login.html`
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<ul>
{{each list}}
<li>{{$value.name}} -- {{$value.age}}</li>
{{/each}}
</ul>
<!--
//后台的模板不是使用script标签来创建模板
整个页面就是模板
-->
<form action="">
用户名: <input type="text"> <br>
密码 <input type="text"> <br>
<button>登陆</button>
</form>
</body>
</html>
```
## node操作mysql
```js
const express = require('express')
// 导入MySQL模块
const mysql = require('mysql');
const app = express()
app.listen('3008', () => {
console.log('http://127.0.0.1:3008')
})
// 创建一个数据库连接, 用createPool不用createConnection
var connection = mysql.createPool({
host: '127.0.0.1', // 数据库服务器的地址
user: 'root', // 数据库的账号
password: 'root', // 数据库的密码
database: 'mybase58' // 数据库名称, 不是表的名称
});
// 连接数据库或打开数据库
//connection.connect();
//不用手动打开链接
app.get('/', (req, res) => {
//查
// let sql = `select * from users`
//增
// let obj = {
// name: '赵六',
// age: '26',
// address: '赵家村'
// }
// let sql = `insert into users (name, age, address) value ('${obj.name}', '${obj.age}', '${obj.address}')`
//注意: value里面的字符串要主动加引号
//除了查询, 增删改的fields没有定义
//查询返回的是所有的数据, 增删改返回的是okPacket
//改
// let obj = {
// id: 8,
// age: 25
// }
// let sql = `update users set age = '${obj.age}' where id = '${obj.id}'`
//删
let obj = {
name: '赵六',
}
let sql = `delete from users where name = '${obj.name}'`
connection.query(sql, (err, results, fields) => {
if (err) {
console.log(err)
throw err
} else {
console.log(results)
console.log(typeof results)
console.log('-----------------')
console.log(fields)
}
})
})
```
简洁写法
```js
//参数查询
let sql = 'select * from users where name = ?'
connection.query(sql, ['张三'], (err, results, fields) => {
if (err) {
console.log(err)
throw err
} else {
console.log(results)
}
})
//增
let obj = {
name: '赵六1212',
age: '26',
address: '赵家村'
}
let sql = `insert into users set ?`
connection.query(sql, obj, (err, results, fields) => {
if (err) {
console.log(err)
throw err
} else {
console.log(results)
}
})
//改
let obj = {
id: 5,
name: '赵六1212',
age: '27',
address: '赵家村'
}
let sql = `update users set ? where id = ?`
connection.query(sql, [obj, obj.id], (err, results, fields) => {
//第二个参数, 如果只有一个是对象, 直接写, 如果是多个参数, 加上方括号成数组
if (err) {
console.log(err)
throw err
} else {
console.log(results)
}
})
//删除
let sql = 'delete from users where id = ?'
connection.query(sql, [2], (err, results, fields) => {
if (err) {
console.log(err)
throw err
} else {
console.log(results)
}
})
let sql = 'delete from users where name = ?'
connection.query(sql, ['李八'], (err, results, fields) => {
if (err) {
console.log(err)
throw err
} else {
console.log(results)
}
})
```