2021-9-28

4-1 http-概述

开发接口(不用任何框架)

  • nodejs 处理 http请求
  • 搭建开发环境
  • 开发接口(暂不连接数据库,暂不考虑登录)

http 请求概述

  • DNS 解析,建立 TCP 连接,发送 http 请求——浏览器来做
  • server 端接收到 http 请求,处理并返回
    • 项目来做
  • 客户端(浏览器)接收到返回数据,处理数据(如渲染页面,执行js)

2021-9-29

4-2 处理 get 请求

nodejs 处理 http 请求

  • get 请求和 querystring
  • post 请求和 postdata
  • 路由

简单示例

image.png

nodejs 处理 get 请求

  • get 请求,即客户端要向 server 端获取数据,如查询博客列表
  • 通过 querystring 来传递数据,如 a.html?a=100&b=200
  • 浏览器直接访问就发送 get 请求

代码示例

image.png
操作一下

  1. const http = require('http')
  2. const qs = require('qs')
  3. const server = http.createServer((req, res) => {
  4. const method = req.method
  5. console.log('method ', method) // GET
  6. const url = req.url // 获取请求的完整 url
  7. console.log('url', url)
  8. req.query = qs.parse(url.split('?')[1]) // 解析 querystring
  9. console.log('req.query', req.query)
  10. res.end(
  11. JSON.stringify(req.query) // 将 querystring 返回
  12. )
  13. })
  14. server.listen(8000)
  15. console.log('ok');

如果在 url 中输入 user=demonlb&keyword=123456
就会显示出来这个值
image.png

http://localhost:8000/?user=demonlb&keyword=123456

当输入 http://localhost:8000/api/blog/list/?user=demonlb&keyword=123456
控制台返回如下
image.png

还有一个固定的请求,浏览器自主发送的:获取favicon
image.png

2021-10-9

4-3 处理post请求

nodejs 处理 post 请求

  • post 请求,即客户端要向 server 端传递数据,如新建博客
  • 通过 post data 来传递数据,后面演示
  • 浏览器无法直接模拟,需要手写 js,或者使用 postman

下载 postman 并使用

代码示例

image.png
接收数据时触发 on 方法,将数据放到 postData 中,如果数据较大就会多次触发。当触发结束的时候就会触发下面的 end ,然后可以打印一下数据,让 res end掉

操作一下

  1. const http = require('http')
  2. const server = http.createServer((req, res) => {
  3. if(req.method === "POST") {
  4. // req 数据格式
  5. console.log('req content-type:', req.headers['content-type'])
  6. // 接收数据
  7. let postData = ''
  8. req.on('data', chunk => {
  9. postData += chunk.toString()
  10. })
  11. req.on('end', () => {
  12. console.log('postData:', postData)
  13. res.end('end')
  14. })
  15. }
  16. })
  17. server.listen(8000)
  18. console.log('OK');

写好代码后,打开 postman 选择新建一个 Request

  • 设置方法为 POST ,url 为http://localhost:8000/
  • image.png
    • 选择 Body 选择数据为 raw 数据(普通的字符串数据),格式为 JSON
    • 然后随便写个数据
  • 然后 控制台 会返回 postData 内容
    • image.png

      nodejs 处理路由

      image.png
      路由即资源唯一的标识

代码示例

image.png
split(‘?’)[0] 截取问号前面的内容:即路由
这个在这就不演示了

问题

存在数据截断问题:https://coding.imooc.com/learn/questiondetail/W48BaYR4Ddl6ALwG.html

2021-10-11

4-4 处理http请求的综合示例

写一个综合实例来展现 4-2 和 4-3 所学的
记得 先 npm install —save qs

Get请求

  1. const http = require('http')
  2. const qs = require('qs')
  3. const server = http.createServer((req, res) => {
  4. const method = req.method
  5. const url = req.url
  6. const path = url.split('?')[0]
  7. const query = qs.parse(url.split('?')[1])
  8. // 设置返回格式为JSON
  9. res.setHeader('Content-type', 'application/json')
  10. // 设置返回的数据
  11. const resData = {
  12. method,
  13. url,
  14. path,
  15. query,
  16. }
  17. // 返回
  18. if(method === 'GET') {
  19. res.end(
  20. JSON.stringify(resData)
  21. )
  22. }
  23. })
  24. server.listen(8000)
  25. console.log('OK')

然后 node app.js
在浏览器中输入
http://localhost:8000/api/blog/list/?user=demonlb&keyword=123456

打开 network 中的 preview 会看到我们设置的返回的数据
image.png

Post 请求

继续写:如果 method 为 POST 的话

  1. if (method === 'POST') {
  2. let postData = ''
  3. req.on('data', chunk => {
  4. postData += chunk.toString()
  5. })
  6. req.on('end', () => {
  7. resData.postData = postData
  8. })
  9. // 返回
  10. res.end(
  11. JSON.stringify(resData)
  12. )
  13. }

然后在 postman 中测试一下

image.png

Post 请求解析

req.on(“data”)是接受来自客户端POSTMAN发送的JSON数据,本地的vscode中的这些req.on(“data”),req.on(“end”)相当于服务端用来处理这些数据。

2021-10-12

4-5 搭建开发环境

  • 从 0 开始搭建,不使用任何框架
  • 使用 nodemon 监测文件变化,自动重启 node
  • 使用 cross-env 来设置环境变量,兼容 mac linux 和 windows

开始搭建

1.新建一个 目录 blog-1,初始化文件 npm init -y,然后新建一个 bin 目录
修改 main 字段为”bin/www.js”

2.在 bin 目录下新建 www.js ,写入一些之前练习过的模块

  1. const http = require('http')
  2. const PORT = 8000
  3. const serverHandle = require('../app')
  4. const server = http.createServer(serverHandle)
  5. server.listen(PORT)

3.在 blog-1 下新建一个 app.js,用来书写具体逻辑
分为app.js和www.js的目的:解耦抽离基本的配置代码和业务代码

  1. const serverHandle = (req, res) => {
  2. // 设置返回格式为JSON格式
  3. res.setHeader('Content-type', 'application/json')
  4. // 设置返回的值
  5. const resData = {
  6. name: 'robinluo',
  7. gender: 'male'
  8. }
  9. // 将 JSON 字符串解析
  10. res.end(
  11. JSON.stringify(resData)
  12. )
  13. }
  14. module.exports = serverHandle
  1. 安装所需的一些工具

    1. npm install nodemon cross-env --save-dev
  2. 安装好 nodemon 和 cross-env 后

改写一下 package.json 中的 scripts

  1. // 写一个新的script,让它用 nodemon 来运行 www.js,然后设置环境为 dev 环境
  2. "dev": "cross-env NODE_ENV=dev nodemon ./bin/www.js"

现在就可以直接 npm run dev
env:process.env.NODE_ENV 可以用来识别当前的环境是 env 还是别的,可以监测是线上还是线下环境

2021-10-13

4-6 初始化路由

  • 初始化路由:根据之前的技术方案设计,做出路由
  • 返回假数据:将路由和数据分离以符合设计原则

接口设计

image.png
开始去写!
在 /blog-1/ 下新建一个 src 文件夹,所有的代码都会写在这个里面
然后继续新建 /blog-1/src/router 和里面的文件 router/blog.js 以及 router/user.js
然后书写逻辑:

blog.js

  1. const handleBlogRouter = (req, res) => {
  2. // 获取 method,url,path
  3. const method = req.method
  4. const url = req.url
  5. const path = url.split('?')[0]
  6. // 获取博客列表
  7. if (method === 'GET' && path === '/api/blog/list') {
  8. return {
  9. msg: '这是获取博客列表的接口'
  10. }
  11. }
  12. // 获取博客详情
  13. if (method === 'GET' && path === '/api/blog/detail'){
  14. return {
  15. msg: '这是获取博客详情的接口'
  16. }
  17. }
  18. // 新建一篇博客
  19. if (method === 'POST' && path === '/api/blog/new') {
  20. return {
  21. msg: '这是新建一篇博客的接口'
  22. }
  23. }
  24. // 更新一篇博客
  25. if (method === 'POST' && path === '/api/blog/update') {
  26. return {
  27. msg: '这是更新一篇博客的接口'
  28. }
  29. }
  30. // 删除一篇博客
  31. if (method === 'POST' && path === '/api/blog/del') {
  32. return {
  33. msg: '这是删除一篇博客的接口'
  34. }
  35. }
  36. }
  37. module.exports = handleBlogRouter

user.js

  1. const handleUserRouter = (req, res) => {
  2. // 获取 method,url,path
  3. const method = req.method
  4. const url = req.url
  5. const path = url.split('?')[0]
  6. // 登录
  7. if (method === 'POST' && path === '/api/user/login') {
  8. return {
  9. msg: '这是登录的接口'
  10. }
  11. }
  12. }
  13. module.exports = handleUserRouter

处理路由

在 app.js 中引入这两个接口导出的js

  1. const handleBlogRouter = require('./src/router/blog')
  2. const handleUserRouter = require('./src/router/user')

然后处理这两种路由和意外情况

  1. // 处理 blog 路由
  2. // 传入 req 和 res 到 blogData 中
  3. const blogData = handleBlogRouter(req, res)
  4. // 如果 blogData 存在的话就获取那个值,并将值转换为字符串
  5. if (blogData) {
  6. res.end(
  7. JSON.stringify(blogData)
  8. )
  9. return
  10. }
  11. // 处理 user 路由
  12. const userData = handleUserRouter(req, res)
  13. if (userData) {
  14. res.end(
  15. JSON.stringify(userData)
  16. )
  17. return
  18. }
  19. // 未命中路由返回 404
  20. // 改写一下头和状态码
  21. res.writeHead(404, {"Content-type": "text/plain"})
  22. res.write("404 not found\n")
  23. res.end()

测试一下:成功
image.png
打开 postman 继续测试 POST 请求
image.png

回顾

1.分离出blog.js user.js ,拆分的理由:对某个特定的功能进行自己的处理,结构简单,处理专一

4-7 开发路由(博客列表路由)(上)

新建一个数据模型
新建文件夹和文件 resModel.js src/model/resModel.js

准备工作-创建基类

  1. // 创建一个基类
  2. class BaseModel {
  3. constructor(data, message) {
  4. // 兼容只传字符串的方式
  5. if (typeof data === 'string') {
  6. this.message = data;
  7. data = null
  8. message = null
  9. }
  10. // 如果有 data 就传 data,如果有 message 就传 message
  11. if (data) {
  12. this.data = data
  13. }
  14. if (message) {
  15. this.message = message
  16. }
  17. }
  18. }
  19. // 创建成功基类
  20. class SuccessModel extends BaseModel {
  21. constructor(data, message) {
  22. super(data, message)
  23. this.errNo = 0
  24. }
  25. }
  26. // 创建失败基类
  27. class ErrorModel extends BaseModel {
  28. constructor(data, message) {
  29. super(data, message)
  30. this.errNo = -1
  31. }
  32. }
  33. // 最后导出
  34. module.exports = {
  35. SuccessModel,
  36. ErrorModel
  37. }

这样写了之后可以做到:
返回数据时规定数据的格式
因此可以根据 errNo 让浏览器知道是返回错误或者正确

  1. res.end(
  2. JSON.stringify({
  3. errNo: -1或者0,
  4. data: {...},
  5. message: 'xxx'
  6. })
  7. )

准备工作-解析 query

在 app.js 中解析query,安装 qs 后引入并写入

  1. const qs = require('qs')
  2. ...
  3. const serverHandle = (req, res) => {
  4. ...
  5. // 解析 query
  6. req.query = qs.parse(url.split('?')[1])
  7. ...
  8. }

准备工作-创建 controller

创建 controller 文件夹和 controller/blog.js 和之前创建的 model 文件夹平级
然后在 blog.js 中 创建 mock 数据

  1. const getList = (author, keyword) => {
  2. // 先返回假数据(格式是正确的)
  3. return [
  4. {
  5. id: 1,
  6. title: '标题A',
  7. content: '内容A',
  8. createTime: 1634116705303,
  9. author: 'zhangsan'
  10. },
  11. {
  12. id: 2,
  13. title: '标题B',
  14. content: '内容B',
  15. createTime: 1634116705304,
  16. author: 'lisi'
  17. }
  18. ]
  19. }
  20. module.exports = {
  21. getList
  22. }

Get请求-解析数据-开发博客列表

在 router/blog.js 中

  • 引入解析 query 中的数据 author 和 keyword
  • 将其作为形参传入 getList 中可以获取在 blog.js 中设置的 mock data
  • 最后引入 SuccessModel 来评价 errNo 来返回完整的请求数据 ```javascript // 引入 getList 和 SuccessModel const { getList } = require(‘../controller/blog’) const { SuccessModel, ErrorModel } = require(‘../model/resModel’) … const handleBlogRouter = (req, res) => { … // 获取博客列表 if (method === ‘GET’ && req.path === ‘/api/blog/list’) { // 获取地址栏中 query 中的数据 const author = req.query.author || ‘’ const keyword = req.query.keyword || ‘’ // 然后根据 query 的值查询 mock 数据 const listData = getList(author, keyword)

    return new SuccessModel(listData) }

}

  1. 拆分 bin controller model router app.js 的目的
  2. - bin/www.js 是连接服务的内容 createServer 的内容
  3. - app.js 是设置基本设置的地方 404 命中那里那里
  4. - router 逻辑层面,并且只管路由
  5. - controller 是最关心数据的层次
  6. <a name="S2x3q"></a>
  7. # 2021-10-17
  8. <a name="fiMfY"></a>
  9. # 4-8 开发路由(博客列表路由)(下)
  10. <a name="gJNJ0"></a>
  11. ## Get请求-解析数据-开发博客详情
  12. 继续在 /controller/blog.js 下开发代码<br />开发假数据
  13. ```javascript
  14. ...
  15. const getDetail = (id) => {
  16. // 先返回假数据
  17. // 假设就返回 id 为 1 的这些内容
  18. return {
  19. id: 1,
  20. title: '标题A',
  21. content: '内容A',
  22. createTime: 1634116705303,
  23. author: 'zhangsan'
  24. }
  25. }

然后导出

  1. module.exports = {
  2. ...
  3. getDetail
  4. }

来到 router/blog.js
修改获取博客详情的代码
image.png

  1. ...
  2. // 获取博客详情
  3. if (method === 'GET' && req.path === '/api/blog/detail'){
  4. const id = req.query.id || ''
  5. const listData = getDetail(id)
  6. return new SuccessModel(listData)
  7. }

image.png
返回成功
Get请求全部成功

Post请求-新建博客

使用之前讲过的 数据流的形式来进行操作
这是异步的,使用 Promise 来操作

  1. if (method === 'POST') {
  2. let postData = ''
  3. req.on('data', chunk => {
  4. postData += chunk.toString()
  5. })
  6. req.on('end', () => {
  7. resData.postData = postData
  8. })
  9. // 返回
  10. res.end(
  11. JSON.stringify(resData)
  12. )
  13. }

插-讲解 Promise

新建 promise-test(文件夹)(nodejs-learning/promise-test)
新建一个 index.js 和文件夹files 里面分别创建 a.json b.json c.json
其中 a.json 写入

  1. {
  2. "next": "b.json",
  3. "msg": "this is a"
  4. }

非 promise 形式读取文件

在 index.js 中写入 读文件的一系列操作

  1. // 首先讲解文件操作
  2. const fs = require('fs')
  3. const path = require('path')
  4. // path.resolve 方法拼接文件名
  5. // __dirname 相当于一个全局变量,指明当前的文件路径
  6. // fullFileName 就可获取绝对路径
  7. const fullFileName = path.resolve(__dirname, 'files', 'a.json')
  8. // 异步的读取文件
  9. fs.readFile(fullFileName, (err, data) => {
  10. // 如果报错了
  11. if (err) {
  12. console.error(err)
  13. return
  14. }
  15. // 如果没错,将数据以二进制的形式打印出来
  16. console.log(data.toString())
  17. })

最后在控制台中打印 index.js
image.png

然后把它封装成一个函数,并调用

  1. // callback 方式获取一个文件的内容
  2. function getFileContent(fileName, callback) {
  3. const fullFileName = path.resolve(__dirname, 'files', fileName)
  4. // 异步的读取文件
  5. fs.readFile(fullFileName, (err, data) => {
  6. // 如果报错了
  7. if (err) {
  8. console.error(err)
  9. return
  10. }
  11. // // 如果没错,将数据以二进制的形式打印出来
  12. // console.log(data.toString())
  13. // 如果没错,将数据传到 callback 中
  14. callback(
  15. JSON.parse(data.toString())
  16. )
  17. })
  18. }
  19. // 测试
  20. getFileContent('a.json', aData => console.log('a data', aData))

image.png
可以循环的调用获取数据

  1. // 测试
  2. getFileContent('a.json', aData => {
  3. console.log('a data', aData)
  4. getFileContent(aData.next, bData => {
  5. console.log('b data', bData)
  6. getFileContent(bData.next, cData => {
  7. console.log('c data', cData)
  8. })
  9. })
  10. })

image.png
这个就是 js 中的一个回调地狱的典型实例

promise 形式读取文件

修改刚才的文件 使用 promise的形式来写
getFileContent(‘a.json’) 是一个 promise对象,.then 获取 aData 的内容

  1. const fs = require('fs')
  2. const path = require('path')
  3. // 用 promise 获取文件内容
  4. function getFileContent(fileName) {
  5. const promise = new Promise((resolve, reject)=> {
  6. const fullFileName = path.resolve(__dirname, 'files', fileName)
  7. // 异步的读取文件
  8. fs.readFile(fullFileName, (err, data) => {
  9. // 如果报错了 执行 reject 函数
  10. if (err) {
  11. reject(err)
  12. }
  13. resolve(
  14. JSON.parse(data.toString())
  15. )
  16. })
  17. })
  18. return promise
  19. }
  20. getFileContent('a.json').then(aData => {
  21. console.log('a data', aData);
  22. }).then()

可以继续写

  1. getFileContent('a.json').then(aData => {
  2. console.log('a data', aData);
  3. return getFileContent(aData.next)
  4. }).then(bData => {
  5. console.log('b data', bData);
  6. return getFileContent(bData.next)
  7. }).then(cData => {
  8. console.log('c data', cData);
  9. })

使用 promise 的好处,将 callback 形式的循环嵌套式的代码变为了 .then 这种链式调用的代码,结构更加清晰,更加简单

前端和后端路由的区别

nodejs 的路由是真正的 http 请求的路由,每个路由地址都代表了一个服务端的资源。 而 vue 或者 react 中的路由,它们的特点是切换路由不会发送 http 请求(这也是 SPA 网页的特点),只会切换页面。因此,可以理解为每个路由代表了一个页面。

4-9 开发路由(处理 POSTData)

在 app.js 中定义处理方法

  1. // 用于处理 post data
  2. const getPostData = (req) => {
  3. const promise = new Promise((resolve, reject)=> {
  4. // 如果是 GET 请求,返回一个空就行了,并不是错误
  5. if (req.method !== 'POST') {
  6. resolve({})
  7. return
  8. }
  9. // 如果 content-type 给错了话,返回一个空就行了,也不是错误
  10. if (req.headers['content-type'] !== 'application/json') {
  11. resolve({})
  12. return
  13. }
  14. if (method === 'POST') {
  15. let postData = ''
  16. req.on('data', chunk => {
  17. postData += chunk.toString()
  18. })
  19. req.on('end', () => {
  20. // 如果数据为空的话,返回一个空对象
  21. if (!postData) {
  22. resolve({})
  23. return
  24. }
  25. resolve(
  26. JSON.parse(resData)
  27. )
  28. })
  29. }
  30. })
  31. return promise
  32. }

具体使用
在获取完 postdata 再获取路由的数据

  1. // 处理 post data
  2. getPostData(req).then(postData => {
  3. req.body = postData
  4. // 处理 blog 路由
  5. // 传入 req 和 res 到 blogData 中
  6. const blogData = handleBlogRouter(req, res)
  7. // 如果 blogData 存在的话就获取那个值,并将值转换为字符串
  8. if (blogData) {
  9. res.end(
  10. JSON.stringify(blogData)
  11. )
  12. return
  13. }
  14. // 处理 user 路由
  15. const userData = handleUserRouter(req, res)
  16. if (userData) {
  17. res.end(
  18. JSON.stringify(userData)
  19. )
  20. return
  21. }
  22. // 未命中路由返回 404
  23. // 改写一下头和状态码
  24. res.writeHead(404, {"Content-type": "text/plain"})
  25. res.write("404 not found\n")
  26. res.end()
  27. })

这样做不会影响 GET 请求,会将 POST 请求的 postData 放到 req.body 中去

4-10 开发路由(新建和更新博客路由)

新建博客路由

在 controller/blog.js 中写入测试方法 newblog
这里的 blog 用来承接 postman 中传入的数据,比如 title,content 内容
当成功时,返回 id 给 postman

  1. const newBlog = (blogData = {}) => {
  2. // blogData 是一个博客对象,包含 title content 属性
  3. console.log('blogData', blogData);
  4. return {
  5. id: 3 // 表示新建博客,插入到数据表里面的 id
  6. }
  7. }

最后导出方法

  1. module.exports = {
  2. ...
  3. newBlog
  4. }

回到 router/blog.js 中 处理新建博客的逻辑

  1. // 新建一篇博客
  2. if (method === 'POST' && req.path === '/api/blog/new') {
  3. const blogData = req.body
  4. const data = newBlog(blogData)
  5. return new SuccessModel(data)
  6. }

最后使用 postman 来进行测试
返回了期望的结果
image.png

更新博客路由

同样的,和新建类似
只不过多一个id
假数据:

  1. const updateBlog = (id, blogData = {}) => {
  2. // blogData 是一个博客对象,包含 title content 属性
  3. console.log('blogData,id', blogData, id);
  4. return true
  5. }

更新路由的逻辑

  1. // 更新一篇博客
  2. if (method === 'POST' && req.path === '/api/blog/update') {
  3. const result = updateBlog(id, req.body)
  4. if (result) {
  5. return new SuccessModel()
  6. } else{
  7. return new ErrorModel('update failed')
  8. }
  9. }

最后测试,测试成功
image.png

2021-10-18

4-11 开发路由(删除博客路由和登录路由)

删除博客路由

删除路由和更新类似

  1. // 删除一篇博客
  2. if (method === 'POST' && req.path === '/api/blog/del') {
  3. const result = deleteBlog(id)
  4. if (result) {
  5. return new SuccessModel()
  6. } else{
  7. return new ErrorModel('delete failed')
  8. }
  9. }
  10. ...
  11. const deleteBlog = (id) => {
  12. // id 就是要删除博客的id
  13. return true
  14. }
  15. module.exports = {
  16. ...
  17. deleteBlog
  18. }

image.png

登录路由

首先创建 user.js (controller/user.js)
写假数据

  1. const loginCheck = (username, password) =>{
  2. // 先使用假数据
  3. if (username === 'zhangsan' && password === '123') {
  4. return true
  5. }
  6. return false;
  7. }
  8. module.exports = {
  9. loginCheck
  10. }

处理数据
/router/user.js

  1. const { loginCheck } = require('../controller/user')
  2. const { SuccessModel, ErrorModel } = require('../model/resModel')
  3. const handleUserRouter = (req, res) => {
  4. // 获取 method,url,path
  5. const method = req.method
  6. // 登录
  7. if (method === 'POST' && req.path === '/api/user/login') {
  8. const { username, password } = req.body
  9. const result = loginCheck(username, password)
  10. if (result) {
  11. return new SuccessModel()
  12. } else {
  13. return new ErrorModel('login failed')
  14. }
  15. }
  16. }
  17. module.exports = handleUserRouter

最后测试
image.png
成功!

总结

  • nodejs 处理 http 请求的常用技能, postman 的使用
  • nodejs 开发博客项目的接口,(未连接数据库,未使用登录)
  • 为何要将 router 和 controller 分开?
    • router 是只管路由相关的(来了什么路由就处理什么数据,至于数据时什么样的和从哪来的都不管,有errNo 可以处理正确错误数据)
    • controller 只管处理数据,路由什么不管,如何包装数据,如何传给客户端不管

4-12 补充:路由和API

API:

  • 前端和后端、不同端(子系统)之间对接的一个术语
  • 应用程序接口(包含:路由地址,输入,输出)
  • 对于API使用者(前端)来说就是一种术语约定或者规范,必须按照这种约定来对接才行(不关心具体实现)
  • 对于API创建者(后端)来说就是对这种规范的具体的实现

    路由:

  • 后端系统内部的一个模块

  • 只是一个url,也就是接口地址(既然API是要提供给其他系统来对接的,没有地址怎么对接)
  • 是API的一部分