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
- 路由
简单示例
nodejs 处理 get 请求
- get 请求,即客户端要向 server 端获取数据,如查询博客列表
- 通过 querystring 来传递数据,如 a.html?a=100&b=200
- 浏览器直接访问就发送 get 请求
代码示例
操作一下
const http = require('http')
const qs = require('qs')
const server = http.createServer((req, res) => {
const method = req.method
console.log('method ', method) // GET
const url = req.url // 获取请求的完整 url
console.log('url', url)
req.query = qs.parse(url.split('?')[1]) // 解析 querystring
console.log('req.query', req.query)
res.end(
JSON.stringify(req.query) // 将 querystring 返回
)
})
server.listen(8000)
console.log('ok');
如果在 url 中输入 user=demonlb&keyword=123456
就会显示出来这个值
http://localhost:8000/?user=demonlb&keyword=123456
当输入 http://localhost:8000/api/blog/list/?user=demonlb&keyword=123456 时
控制台返回如下
还有一个固定的请求,浏览器自主发送的:获取favicon
2021-10-9
4-3 处理post请求
nodejs 处理 post 请求
- post 请求,即客户端要向 server 端传递数据,如新建博客
- 通过 post data 来传递数据,后面演示
- 浏览器无法直接模拟,需要手写 js,或者使用 postman
代码示例
接收数据时触发 on 方法,将数据放到 postData 中,如果数据较大就会多次触发。当触发结束的时候就会触发下面的 end ,然后可以打印一下数据,让 res end掉
操作一下
const http = require('http')
const server = http.createServer((req, res) => {
if(req.method === "POST") {
// req 数据格式
console.log('req content-type:', req.headers['content-type'])
// 接收数据
let postData = ''
req.on('data', chunk => {
postData += chunk.toString()
})
req.on('end', () => {
console.log('postData:', postData)
res.end('end')
})
}
})
server.listen(8000)
console.log('OK');
写好代码后,打开 postman 选择新建一个 Request
- 设置方法为 POST ,url 为http://localhost:8000/
- 选择 Body 选择数据为 raw 数据(普通的字符串数据),格式为 JSON
- 然后随便写个数据
- 然后 控制台 会返回 postData 内容
代码示例
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请求
const http = require('http')
const qs = require('qs')
const server = http.createServer((req, res) => {
const method = req.method
const url = req.url
const path = url.split('?')[0]
const query = qs.parse(url.split('?')[1])
// 设置返回格式为JSON
res.setHeader('Content-type', 'application/json')
// 设置返回的数据
const resData = {
method,
url,
path,
query,
}
// 返回
if(method === 'GET') {
res.end(
JSON.stringify(resData)
)
}
})
server.listen(8000)
console.log('OK')
然后 node app.js
在浏览器中输入
http://localhost:8000/api/blog/list/?user=demonlb&keyword=123456
打开 network 中的 preview 会看到我们设置的返回的数据
Post 请求
继续写:如果 method 为 POST 的话
if (method === 'POST') {
let postData = ''
req.on('data', chunk => {
postData += chunk.toString()
})
req.on('end', () => {
resData.postData = postData
})
// 返回
res.end(
JSON.stringify(resData)
)
}
然后在 postman 中测试一下
- 输入url: http://localhost:8000/api/blog/update?id=1
- 然后更新一下 请求
- 可以看到设置返回的一些值
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 ,写入一些之前练习过的模块
const http = require('http')
const PORT = 8000
const serverHandle = require('../app')
const server = http.createServer(serverHandle)
server.listen(PORT)
3.在 blog-1 下新建一个 app.js,用来书写具体逻辑
分为app.js和www.js的目的:解耦抽离基本的配置代码和业务代码
const serverHandle = (req, res) => {
// 设置返回格式为JSON格式
res.setHeader('Content-type', 'application/json')
// 设置返回的值
const resData = {
name: 'robinluo',
gender: 'male'
}
// 将 JSON 字符串解析
res.end(
JSON.stringify(resData)
)
}
module.exports = serverHandle
安装所需的一些工具
npm install nodemon cross-env --save-dev
安装好 nodemon 和 cross-env 后
改写一下 package.json 中的 scripts
// 写一个新的script,让它用 nodemon 来运行 www.js,然后设置环境为 dev 环境
"dev": "cross-env NODE_ENV=dev nodemon ./bin/www.js"
现在就可以直接 npm run dev
env:process.env.NODE_ENV 可以用来识别当前的环境是 env 还是别的,可以监测是线上还是线下环境
2021-10-13
4-6 初始化路由
- 初始化路由:根据之前的技术方案设计,做出路由
- 返回假数据:将路由和数据分离以符合设计原则
接口设计
开始去写!
在 /blog-1/ 下新建一个 src 文件夹,所有的代码都会写在这个里面
然后继续新建 /blog-1/src/router 和里面的文件 router/blog.js 以及 router/user.js
然后书写逻辑:
blog.js
const handleBlogRouter = (req, res) => {
// 获取 method,url,path
const method = req.method
const url = req.url
const path = url.split('?')[0]
// 获取博客列表
if (method === 'GET' && path === '/api/blog/list') {
return {
msg: '这是获取博客列表的接口'
}
}
// 获取博客详情
if (method === 'GET' && path === '/api/blog/detail'){
return {
msg: '这是获取博客详情的接口'
}
}
// 新建一篇博客
if (method === 'POST' && path === '/api/blog/new') {
return {
msg: '这是新建一篇博客的接口'
}
}
// 更新一篇博客
if (method === 'POST' && path === '/api/blog/update') {
return {
msg: '这是更新一篇博客的接口'
}
}
// 删除一篇博客
if (method === 'POST' && path === '/api/blog/del') {
return {
msg: '这是删除一篇博客的接口'
}
}
}
module.exports = handleBlogRouter
user.js
const handleUserRouter = (req, res) => {
// 获取 method,url,path
const method = req.method
const url = req.url
const path = url.split('?')[0]
// 登录
if (method === 'POST' && path === '/api/user/login') {
return {
msg: '这是登录的接口'
}
}
}
module.exports = handleUserRouter
处理路由
在 app.js 中引入这两个接口导出的js
const handleBlogRouter = require('./src/router/blog')
const handleUserRouter = require('./src/router/user')
然后处理这两种路由和意外情况
// 处理 blog 路由
// 传入 req 和 res 到 blogData 中
const blogData = handleBlogRouter(req, res)
// 如果 blogData 存在的话就获取那个值,并将值转换为字符串
if (blogData) {
res.end(
JSON.stringify(blogData)
)
return
}
// 处理 user 路由
const userData = handleUserRouter(req, res)
if (userData) {
res.end(
JSON.stringify(userData)
)
return
}
// 未命中路由返回 404
// 改写一下头和状态码
res.writeHead(404, {"Content-type": "text/plain"})
res.write("404 not found\n")
res.end()
测试一下:成功
打开 postman 继续测试 POST 请求
回顾
1.分离出blog.js user.js ,拆分的理由:对某个特定的功能进行自己的处理,结构简单,处理专一
4-7 开发路由(博客列表路由)(上)
新建一个数据模型
新建文件夹和文件 resModel.js src/model/resModel.js
准备工作-创建基类
// 创建一个基类
class BaseModel {
constructor(data, message) {
// 兼容只传字符串的方式
if (typeof data === 'string') {
this.message = data;
data = null
message = null
}
// 如果有 data 就传 data,如果有 message 就传 message
if (data) {
this.data = data
}
if (message) {
this.message = message
}
}
}
// 创建成功基类
class SuccessModel extends BaseModel {
constructor(data, message) {
super(data, message)
this.errNo = 0
}
}
// 创建失败基类
class ErrorModel extends BaseModel {
constructor(data, message) {
super(data, message)
this.errNo = -1
}
}
// 最后导出
module.exports = {
SuccessModel,
ErrorModel
}
这样写了之后可以做到:
返回数据时规定数据的格式
因此可以根据 errNo 让浏览器知道是返回错误或者正确
res.end(
JSON.stringify({
errNo: -1或者0,
data: {...},
message: 'xxx'
})
)
准备工作-解析 query
在 app.js 中解析query,安装 qs 后引入并写入
const qs = require('qs')
...
const serverHandle = (req, res) => {
...
// 解析 query
req.query = qs.parse(url.split('?')[1])
...
}
准备工作-创建 controller
创建 controller 文件夹和 controller/blog.js 和之前创建的 model 文件夹平级
然后在 blog.js 中 创建 mock 数据
const getList = (author, keyword) => {
// 先返回假数据(格式是正确的)
return [
{
id: 1,
title: '标题A',
content: '内容A',
createTime: 1634116705303,
author: 'zhangsan'
},
{
id: 2,
title: '标题B',
content: '内容B',
createTime: 1634116705304,
author: 'lisi'
}
]
}
module.exports = {
getList
}
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) }
}
拆分 bin controller model router app.js 的目的
- bin/www.js 是连接服务的内容 ,createServer 的内容
- app.js 是设置基本设置的地方 404 命中那里那里
- router 逻辑层面,并且只管路由
- controller 是最关心数据的层次
<a name="S2x3q"></a>
# 2021-10-17
<a name="fiMfY"></a>
# 4-8 开发路由(博客列表路由)(下)
<a name="gJNJ0"></a>
## Get请求-解析数据-开发博客详情
继续在 /controller/blog.js 下开发代码<br />开发假数据
```javascript
...
const getDetail = (id) => {
// 先返回假数据
// 假设就返回 id 为 1 的这些内容
return {
id: 1,
title: '标题A',
content: '内容A',
createTime: 1634116705303,
author: 'zhangsan'
}
}
然后导出
module.exports = {
...
getDetail
}
来到 router/blog.js
修改获取博客详情的代码
...
// 获取博客详情
if (method === 'GET' && req.path === '/api/blog/detail'){
const id = req.query.id || ''
const listData = getDetail(id)
return new SuccessModel(listData)
}
Post请求-新建博客
使用之前讲过的 数据流的形式来进行操作
这是异步的,使用 Promise 来操作
if (method === 'POST') {
let postData = ''
req.on('data', chunk => {
postData += chunk.toString()
})
req.on('end', () => {
resData.postData = postData
})
// 返回
res.end(
JSON.stringify(resData)
)
}
插-讲解 Promise
新建 promise-test(文件夹)(nodejs-learning/promise-test)
新建一个 index.js 和文件夹files 里面分别创建 a.json b.json c.json
其中 a.json 写入
{
"next": "b.json",
"msg": "this is a"
}
非 promise 形式读取文件
在 index.js 中写入 读文件的一系列操作
// 首先讲解文件操作
const fs = require('fs')
const path = require('path')
// path.resolve 方法拼接文件名
// __dirname 相当于一个全局变量,指明当前的文件路径
// fullFileName 就可获取绝对路径
const fullFileName = path.resolve(__dirname, 'files', 'a.json')
// 异步的读取文件
fs.readFile(fullFileName, (err, data) => {
// 如果报错了
if (err) {
console.error(err)
return
}
// 如果没错,将数据以二进制的形式打印出来
console.log(data.toString())
})
最后在控制台中打印 index.js
然后把它封装成一个函数,并调用
// callback 方式获取一个文件的内容
function getFileContent(fileName, callback) {
const fullFileName = path.resolve(__dirname, 'files', fileName)
// 异步的读取文件
fs.readFile(fullFileName, (err, data) => {
// 如果报错了
if (err) {
console.error(err)
return
}
// // 如果没错,将数据以二进制的形式打印出来
// console.log(data.toString())
// 如果没错,将数据传到 callback 中
callback(
JSON.parse(data.toString())
)
})
}
// 测试
getFileContent('a.json', aData => console.log('a data', aData))
可以循环的调用获取数据
// 测试
getFileContent('a.json', aData => {
console.log('a data', aData)
getFileContent(aData.next, bData => {
console.log('b data', bData)
getFileContent(bData.next, cData => {
console.log('c data', cData)
})
})
})
promise 形式读取文件
修改刚才的文件 使用 promise的形式来写
getFileContent(‘a.json’) 是一个 promise对象,.then 获取 aData 的内容
const fs = require('fs')
const path = require('path')
// 用 promise 获取文件内容
function getFileContent(fileName) {
const promise = new Promise((resolve, reject)=> {
const fullFileName = path.resolve(__dirname, 'files', fileName)
// 异步的读取文件
fs.readFile(fullFileName, (err, data) => {
// 如果报错了 执行 reject 函数
if (err) {
reject(err)
}
resolve(
JSON.parse(data.toString())
)
})
})
return promise
}
getFileContent('a.json').then(aData => {
console.log('a data', aData);
}).then()
可以继续写
getFileContent('a.json').then(aData => {
console.log('a data', aData);
return getFileContent(aData.next)
}).then(bData => {
console.log('b data', bData);
return getFileContent(bData.next)
}).then(cData => {
console.log('c data', cData);
})
使用 promise 的好处,将 callback 形式的循环嵌套式的代码变为了 .then 这种链式调用的代码,结构更加清晰,更加简单
前端和后端路由的区别
nodejs 的路由是真正的 http 请求的路由,每个路由地址都代表了一个服务端的资源。 而 vue 或者 react 中的路由,它们的特点是切换路由不会发送 http 请求(这也是 SPA 网页的特点),只会切换页面。因此,可以理解为每个路由代表了一个页面。
4-9 开发路由(处理 POSTData)
在 app.js 中定义处理方法
// 用于处理 post data
const getPostData = (req) => {
const promise = new Promise((resolve, reject)=> {
// 如果是 GET 请求,返回一个空就行了,并不是错误
if (req.method !== 'POST') {
resolve({})
return
}
// 如果 content-type 给错了话,返回一个空就行了,也不是错误
if (req.headers['content-type'] !== 'application/json') {
resolve({})
return
}
if (method === 'POST') {
let postData = ''
req.on('data', chunk => {
postData += chunk.toString()
})
req.on('end', () => {
// 如果数据为空的话,返回一个空对象
if (!postData) {
resolve({})
return
}
resolve(
JSON.parse(resData)
)
})
}
})
return promise
}
具体使用
在获取完 postdata 再获取路由的数据
// 处理 post data
getPostData(req).then(postData => {
req.body = postData
// 处理 blog 路由
// 传入 req 和 res 到 blogData 中
const blogData = handleBlogRouter(req, res)
// 如果 blogData 存在的话就获取那个值,并将值转换为字符串
if (blogData) {
res.end(
JSON.stringify(blogData)
)
return
}
// 处理 user 路由
const userData = handleUserRouter(req, res)
if (userData) {
res.end(
JSON.stringify(userData)
)
return
}
// 未命中路由返回 404
// 改写一下头和状态码
res.writeHead(404, {"Content-type": "text/plain"})
res.write("404 not found\n")
res.end()
})
这样做不会影响 GET 请求,会将 POST 请求的 postData 放到 req.body 中去
4-10 开发路由(新建和更新博客路由)
新建博客路由
在 controller/blog.js 中写入测试方法 newblog
这里的 blog 用来承接 postman 中传入的数据,比如 title,content 内容
当成功时,返回 id 给 postman
const newBlog = (blogData = {}) => {
// blogData 是一个博客对象,包含 title content 属性
console.log('blogData', blogData);
return {
id: 3 // 表示新建博客,插入到数据表里面的 id
}
}
最后导出方法
module.exports = {
...
newBlog
}
回到 router/blog.js 中 处理新建博客的逻辑
// 新建一篇博客
if (method === 'POST' && req.path === '/api/blog/new') {
const blogData = req.body
const data = newBlog(blogData)
return new SuccessModel(data)
}
更新博客路由
同样的,和新建类似
只不过多一个id
假数据:
const updateBlog = (id, blogData = {}) => {
// blogData 是一个博客对象,包含 title content 属性
console.log('blogData,id', blogData, id);
return true
}
更新路由的逻辑
// 更新一篇博客
if (method === 'POST' && req.path === '/api/blog/update') {
const result = updateBlog(id, req.body)
if (result) {
return new SuccessModel()
} else{
return new ErrorModel('update failed')
}
}
最后测试,测试成功
2021-10-18
4-11 开发路由(删除博客路由和登录路由)
删除博客路由
删除路由和更新类似
// 删除一篇博客
if (method === 'POST' && req.path === '/api/blog/del') {
const result = deleteBlog(id)
if (result) {
return new SuccessModel()
} else{
return new ErrorModel('delete failed')
}
}
...
const deleteBlog = (id) => {
// id 就是要删除博客的id
return true
}
module.exports = {
...
deleteBlog
}
登录路由
首先创建 user.js (controller/user.js)
写假数据
const loginCheck = (username, password) =>{
// 先使用假数据
if (username === 'zhangsan' && password === '123') {
return true
}
return false;
}
module.exports = {
loginCheck
}
处理数据
/router/user.js
const { loginCheck } = require('../controller/user')
const { SuccessModel, ErrorModel } = require('../model/resModel')
const handleUserRouter = (req, res) => {
// 获取 method,url,path
const method = req.method
// 登录
if (method === 'POST' && req.path === '/api/user/login') {
const { username, password } = req.body
const result = loginCheck(username, password)
if (result) {
return new SuccessModel()
} else {
return new ErrorModel('login failed')
}
}
}
module.exports = handleUserRouter
最后测试
成功!
总结
- nodejs 处理 http 请求的常用技能, postman 的使用
- nodejs 开发博客项目的接口,(未连接数据库,未使用登录)
- 为何要将 router 和 controller 分开?
- router 是只管路由相关的(来了什么路由就处理什么数据,至于数据时什么样的和从哪来的都不管,有errNo 可以处理正确错误数据)
- controller 只管处理数据,路由什么不管,如何包装数据,如何传给客户端不管