[TOC]
>[success] # 初始化并且開發路由
* **初始化路由** :根據之前的 [技術方案](http://www.hmoore.net/wangjiachong/nodejs/2649930) 的設計,做出路由
* **返回假數據**: **將路由和假數據處理分離**,以符合設計原則
* **這里有2個問題** :
1. **什么是路由?路由其實就是接口** , 下面斜線后面的都算是 **路由(接口)**

2. **為什么要返回假數據呢?** 因為 **目前還沒有學到數據庫,這是一個循循漸進的過程** ,暫時先使用返回假數據,后期學完數據庫后把假數據替換成真數據。
>[success] ## 初始化路由

之前在 **項目需求分析** 時候,我們就了解到接口需要這么多,總共分為 **2 個類型** ,一個是 **blog 博客類型** ,一個是 **user 類型** ,所以我們要通過 **2 個文件(模塊)** 來實現這 **6** 個接口(**blog 博客類型 5個接口** ,**user 類型 1個接口**),接下來我們在項目文件夾中創建一個 **src** 文件夾,然后在 **src** 下創建 **router 文件夾**,然后在 **router 文件夾** 中創建 **blog.js** 與 **user.js**
**文件目錄結構**:
~~~
blog-1 // 項目文件夾
|__ bin // 可執行文件文件夾
| |__ www.js // 項目啟動入口文件
|__ src // 放接口的文件夾
| |__ router // 路由(接口)文件夾
| |__ blog.js // 【博客類型】有關的接口都寫在這里
| |__ user.js // 【用戶類型】有關的接口都寫在這里
|
|__ app.js // 處理server業務邏輯的方法的文件
|__ package.json // 初始化項目文件
~~~
**src/router/blog.js**:
~~~
const handleBlogRouter = (req, res) => {
const method = req.method // GET POST
// 獲取博客列表
if(method === 'GET' && req.path === '/api/blog/list'){
return {
msg: '這是獲取博客列表的接口' // 后期從數據庫中取數據返回給前端
}
}
// 獲取博客詳情
if(method === 'GET' && req.path === '/api/blog/detail'){
return {
msg: '這是獲取博客詳情的接口'
}
}
// 新建一篇博客
if(method === 'POST' && req.path === '/api/blog/new'){
return {
msg: '這是新建博客的接口'
}
}
// 更新一篇博客
if(method === 'POST' && req.path === '/api/blog/update'){
return {
msg: '這是更新博客的接口'
}
}
// 刪除一篇博客
if(method === 'POST' && req.path === '/api/blog/delete'){
return {
msg: '這是刪除博客的接口'
}
}
}
module.exports = handleBlogRouter
~~~
**src/router/user.js**:
~~~
const handleUserRouter = (req, res) => {
const method = req.method // GET POST
// 登陸
if(method === 'POST' && req.path === '/api/user/login'){
return {
msg: '這是登陸博客的接口'
}
}
}
module.exports = handleUserRouter
~~~
然后在 **app.js** 中引入這兩個方法,雖然目前在 **app.js** 中 **處理 blog 與 user 路由** 這種每次都需要判斷的方法不是非常的正規,會覺得非常的繁瑣,這就是 **原生 nodejs 不使用任何框架** 開發的不好處,后期用 **express** 或者 **Koa2** 開發時候就好了,先忍耐一下。
**app.js** :
~~~
// 引入處理路由文件
const handleBlogRouter = require('./src/router/blog')
const handleUserRouter = require('./src/router/user')
const serverHandle = (req, res) => {
// 設置返回格式 JSON
res.setHeader('Content-type', 'application/json')
// 獲取 path
const url = req.url
req.path = url.split('?')[0]
// 處理 blog 路由
const blogData = handleBlogRouter(req, res)
if(blogData){
res.end(
JSON.stringify(blogData)
)
return // 這里需要return,不然返回數據后還會往下走
}
// 處理 user 路由
const userData = handleUserRouter(req, res)
if(userData){
res.end(
JSON.stringify(userData)
)
return
}
// 未命中路由,返回 404
res.writeHead(404, {"Content-type": "text/plain"}) // 把響應頭狀態碼設置為404,然后Content-type: text/plain是純文本,為了覆蓋上面的json類型
// 返回內容
res.write('404 Not Found\n')
res.end()
}
module.exports = serverHandle
~~~
此時就可以測試一下是否好用了,試一下訪問詳情接口, `http://127.0.0.1:8000/api/blog/detail`

接下來訪問一下 **404 頁面** ,直接瀏覽器輸入 `http://127.0.0.1:8000`

可以看到進入了 **404 頁面** ,這樣我們的初步就算完成了。
>[success] ## 開發路由
在開發路由之前,我們先創建一個 **數據模型** ,什么是 **數據模型** ?
**數據模型:就是用來描述數據、組織數據和對數據進行操作** ,然后 **通過一個方法或者一個類返回一個我們想要的一個數據格式,可以理解成就是一個方法可以返回我們想要的數據結構**,例如:**下面代碼塊中,假如 data 是我們從數據庫中查出來的數據,我們把查出來的數據通過這個數據模型的方法處理之后,就會返回一個既有 data 也有 errno 字段的固定數據結構的對象** 。
**成功時返回的數據結構**
~~~
{
data: [...]
errno: 0,
message: "獲取成功"
}
~~~
**失敗時返回的數據結構**
~~~
{
errno: -1
message: "獲取失敗,系統錯誤"
}
~~~
這樣前端就可以很方便的通過 **errno** 屬性來判斷獲取數據是成功還是失敗, **成功了就展示數據,錯誤了就提示 massage 中的錯誤提示**。
>[success] ### 創建數據模型
接下來 **創建** 一個 **存放數據模型代碼的文件夾和文件** ,在原有項目中 **src文件夾** 下 **創建 model 文件夾 和 resModel.js 文件**
**文件目錄結構**:
~~~
blog-1 // 項目文件夾
|__ bin // 可執行文件文件夾
| |__ www.js // 項目啟動入口文件
|
|__ src // 放接口的文件夾
| |__ router // 路由(接口)文件夾
| | |__ blog.js // 【博客類型】有關的接口都寫在這里
| | |__ user.js // 【用戶類型】有關的接口都寫在這里
| |
| |__ model // 【存放數據模型】的文件夾
| |__ resModel.js // 【數據模型類】文件
|
|__ app.js // 處理server業務邏輯的方法的文件
|__ package.json // 初始化項目文件
~~~
**src/model/resModel.js**
~~~
// 基類
class BaseModel {
constructor(data, message) {
if(typeof data === 'string'){ // 這里是做一個兼容
this.message = data
data = null
message = null
}
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
}
~~~
>[success] ### 使用數據模型
**數據模型** 是在給前端返回數據時才會用到,想返回數據就需要先根據請求參數去數據庫取數據,所以我們先著手接口,接口在使用時候 **需要解析參數** ,我們之前寫的 **app.js** 中還沒有解析 **query 參數 與 post data 參數** ,首先我們需要改一下 **app.js**
1. **解析 query 參數** 跟 **post data 參數**
**app.js**
~~~
const querystring = require('querystring')
// 引入處理路由文件
const handleBlogRouter = require('./src/router/blog')
const handleUserRouter = require('./src/router/user')
// 用于處理 post data
const getPostData = (req) => {
const promise = new Promise((resolve, reject) => {
if(req.method !== 'POST'){ // 不等于 post 請求
resolve({})
return
}
if(req.headers['content-type'] !== 'application/json'){ // 傳入的數據格式不是 application/json ,可能是form-data的參數
resolve({})
return
}
// 1. POST通過數據流方式接收數據
let postData = ''
// 2. 給req注冊data事件,當有數據提交過來就會觸發,事件的作用是接收數據,接收大量數據的時候,是分塊接收的
req.on('data', chunk => {
postData += chunk.toString()
})
// 3. 給req注冊end事件,當數據全部接收完畢,會觸發
req.on('end', () => {
if(!postData){ // 無數據返回空對象
resolve({})
return
}
resolve(
JSON.parse(postData)
)
})
})
return promise
}
const serverHandle = (req, res) => {
// 設置返回格式 JSON
res.setHeader('Content-type', 'application/json')
// 獲取 path
const url = req.url
req.path = url.split('?')[0]
// 解析query 參數
req.query = querystring.parse(url.split('?')[1])
// 處理 post data 參數
getPostData(req).then(postData => {
req.body = postData
// 處理 blog 路由
const blogData = handleBlogRouter(req, res)
if(blogData){
res.end(
JSON.stringify(blogData)
)
return // 這里需要return,不然返回數據后還會往下走
}
// 處理 user 路由
const userData = handleUserRouter(req, res)
if(userData){
res.end(
JSON.stringify(userData)
)
return
}
// 未命中路由,返回 404
res.writeHead(404, {"Content-type": "text/plain"}) // 把響應頭狀態碼設置為404,然后Content-type: text/plain是純文本,為了覆蓋上面的json類型
// 返回內容
res.write('404 Not Found\n')
res.end()
})
}
module.exports = serverHandle
~~~
解析完了 **query 與 post data 參數** ,來看下圖,我們 **處理 router 文件夾中的路由接口的文件** 中返回的數據都是當初寫死的,要獲取數據還是要從數據庫中獲取,但是 **直接在這個文件中寫獲取數據的處理數據邏輯會很混亂** ,這時候,就要 **分出來一個 controller 文件來專門處理數據**

所以我們新建一個 **controller 文件夾** ,然后在里面創建 **blog.js** 和 **user.js** 文件
~~~
blog-1 // 項目文件夾
|__ bin // 可執行文件文件夾
| |__ www.js // 項目啟動入口文件
|
|__ src // 放接口的文件夾
| |__ router // 路由(接口)文件夾
| | |__ blog.js // 【博客類型】有關的接口都寫在這里
| | |__ user.js // 【用戶類型】有關的接口都寫在這里
| |
| |__ model // 【存放數據模型】的文件夾
| | |__ resModel.js // 【數據模型類】文件
| |
| |__ controller // 【從數據庫取出數據,然后處理數據,返回給路由層】
| |__ blog.js // 【博客類型】處理數據的文件
| |__ user.js // 【用戶類型】處理數據的文件
|
|__ app.js // 處理server業務邏輯的方法的文件
|__ package.json // 初始化項目文件
~~~
然后在 **controller 中的 blog.js 與 user.js文件** 中進行 **從數據庫(controller 這里先假裝使用數據庫查詢出來的數據)獲取數據的操作,以及處理數據的邏輯,處理完成后再返回給對應路由的方法中**
**src/controller/blog.js**
~~~
/**
* 獲取博客列表
* @param {string} author - 作者
* @param {string} keyword - 搜索關鍵字
*/
const getList = (author, keyword) => {
// 先返回假數據(格式是正確的)
// author, keyword這2個參數等開始學習數據庫查詢時候會用到,目前先假裝已經使用了author, keyword 屬性查出來的下列數據。
return [
{
id: 1,
title: '標題A',
content: '內容A',
createTime: '1645696670000',
author: 'zhangsan'
},
{
id: 2,
title: '標題B',
content: '內容B',
createTime: '1645696790000',
author: 'lisi'
}
]
}
/**
* 獲取博客詳情
* @param {number} id - 博客列表 item id
*/
const getDetail = (id) => {
return {
id: 1,
title: '標題A',
content: '內容A',
createTime: '1645696670000',
author: 'zhangsan'
}
}
/**
* 新增博客
* @param {object} blogData - blogData 是一個博客對象,包含 title content author 屬性
*/
const newBlog = (blogData = {}) => {
// 這里假裝已經向數據庫進行了新增操作
return {
id: 3 // 表示新建博客,插入到數據表里面的id
}
}
/**
* 更新博客
* @param {number} id - 要更新的博客的id
* @param {object} blogData - blogData 是一個博客對象,包含 title content 屬性
*/
const updateBlog = (id, blogData = {}) => {
// 這里假裝已經向數據庫進行了更新操作
return true
}
/**
* 刪除博客
* @param {number} id - 要刪除的博客的id
* @param {string} author - 作者身份,因為不可以越權刪除,所以要加作者
*/
const deleteBlog = (id) => {
// 這里假裝已經向數據庫進行了刪除操作
return true
}
module.exports = {
getList,
getDetail,
newBlog,
updateBlog,
deleteBlog
}
~~~
**src/controller/user.js**
~~~
/**
* 登陸
* @param {string} username - 用戶名
* @param {string} password - 密碼
*/
const login = (username, password) => {
// 先使用假數據
if(username === 'zhangsan' && password === '123'){
return true
}
return false
}
module.exports = { login }
~~~
2. 開始使用 **數據模型**
在 **blog 和 user** 這2個路由文件中使用數據模型 **SuccessModel** 和 **ErrorModel** 這2個類來處理數據格式
**src/router/blog.js**
~~~
// 從 controller 中獲取數據
const { getList,
getDetail,
newBlog,
updateBlog,
deleteBlog } = require('../controller/blog')
// 引入數據模型
const { SuccessModel, ErrorModel } = require('../model/resModel')
const handleBlogRouter = (req, res) => {
const method = req.method // GET POST
const id = req.query.id
// 獲取博客列表
if(method === 'GET' && req.path === '/api/blog/list'){
// 取得參數
const author = req.query.author || ''
const keyword = req.query.keyword || ''
// 從 controller 中獲取數據
const listData = getList(author, keyword)
// 通過數據模型加工數據返回帶有 erron 屬性的對象數據
return new SuccessModel(listData)
}
// 獲取博客詳情
if(method === 'GET' && req.path === '/api/blog/detail'){
const data = getDetail(id)
return new SuccessModel(data)
}
// 新建一篇博客
if(method === 'POST' && req.path === '/api/blog/new'){
const data = newBlog(req.body)
return new SuccessModel(data)
}
// 更新一篇博客
if(method === 'POST' && req.path === '/api/blog/update'){
const result = updateBlog(id, req.body)
if(result){ // 更新成功
return new SuccessModel()
} else {
return new ErrorModel('更新博客失敗')
}
}
// 刪除一篇博客
if(method === 'POST' && req.path === '/api/blog/delete'){
const result = deleteBlog(id)
if(result){ // 刪除成功
return new SuccessModel()
} else {
return new ErrorModel('刪除博客失敗')
}
}
}
module.exports = handleBlogRouter
~~~
**src/router/user.js**
~~~
// 從 controller 中獲取數據
const { login } = require('../controller/user')
// 引入數據模型
const { SuccessModel, ErrorModel } = require('../model/resModel')
const handleUserRouter = (req, res) => {
const method = req.method // GET POST
// 登陸
if(method === 'POST' && req.path === '/api/user/login'){
const { username, password } = req.body
const result = login(username, password)
if(result){ // 登陸成功
return new SuccessModel()
} else {
return new ErrorModel('登陸失敗')
}
}
}
module.exports = handleUserRouter
~~~
>[success] ## 總結
我們目前分了 **4 層結構, 每層都有自己的職責劃分,處理每層的事務**
1. **www.js** 都是一些 **createServer** 邏輯
2. **app.js** 是系統基本設置的一個文件,例如:**設置數據返回類型 、獲取path、解析query、處理路由、處理未命中 404**
3. **router 里面只管路由,返回給正確的格式數據,至于數據返回的是什么、如何計算的、怎么篩選的它不管,它只關心和路由有關的事情,具體的數據,怎么摘出來,怎么篩選出來,數據是正確還是錯誤的它都不管**
4. **controller 層是最關心數據的一層,在 controller 中會進行數據庫的查詢以及對數據的計算和篩選等處理**
我們接收到一個請求后會按照這個流程 **www.js 》app.js 》router路由 》controller** 來走,從 **www.js 一步步到 controller**,再從 **controller** 取到數據后層層向上傳遞直到傳到 **www.js** ,再通過 **www.js** 的 **res.end** 方法將數據返回給瀏覽器客戶端,這一切就仿佛在畫一個圓圈一樣,**從起點畫到終點,再從終點往回畫到起點**。

- NodeJS基礎
- 什么是NodeJS
- npm
- Node.js+Express+Koa2+開發Web Server博客
- 下載和安裝node
- nodejs和js的區別
- commonjs-演示
- nodejs如何debugger
- server端與前端的區別
- 項目需求分析
- 開發接口(不使用任何框架)
- http-概述
- 處理get請求
- 處理post請求
- 處理http請求的綜合示例
- 搭建開發環境
- 初始化并且開發路由
- 開發博客項目之數據存儲
- MySql介紹
- 數據庫操作(創建和增、刪、查)
- Nodejs 操作 Mysql
- Nodejs 鏈接 mysql 做成工具
- API 對接 MySQL
- 開發博客項目之登陸
- cookie-介紹
- cookie用于登錄驗證
- cookie做限制