4-5 搭建开发环境
- 从0开始搭建,不使用任何框架
- 使用nodemon监测文件变化,自动重启node(不需要像之前那样使用
node xxx.js
来手动重启node) - 使用cross-env设置环境变量,兼容mac linux和windows
开始搭建
根据上一篇笔记的“模块化规范”初始npm环境,并在package.json
文件中继续以下步骤:
nodemon
安装nodemon:npm install nodemon --save
或者npm i nodemon -D
启动nodemon:nodemon app.js
(用nodemon替换node去启动项目的入口文件将项目改变成自动重启服务器)
当你看到每次保存以后终端都自动更新,说明自启动成功:
后续启动项目:(package.json
中的配置写在了下面)npm run dev
cross-env
安装cross-env:npm install cross-env --save-dev
或者npm i cross-env -D
手动补齐package.json
:
1 | { |
- 在node中,process对象是全局变量,它提供当前node.js的有关信息,以及控制当前node.js的有关进程。因为是全局变量,它对于node应用程序是始终可用的,无需require()。
- env是process的一个属性,这个属性返回包含用户环境信息的对象。在终端输入node后,在输入process.env可以看到打印出来的信息。
NODE_ENV
不是process.env
对象上原有的属性,它是我们自己添加上去的一个环境变量,用来确定当前所处的开发阶段。- 一般 生产阶段 设为production,开发阶段 设为develop(在
package.json
中设置),然后在 脚本 中读取process.env.NODE_ENV
即可知道现在是开发阶段还是生产阶段。
- 一般 生产阶段 设为production,开发阶段 设为develop(在
app.js
:
1 | const serverHandle = (req, res) => { |
./bin/www.js
:
1 | const http = require("http") |
千万不要忘记**终端执行npm run dev
**!!
测试
4-6 初始化路由 开发接口
初始化路由:根据之前技术方案的设计,做出路由
返回假数据:将路由和数据处理分离,以符合设计原则
注意:
- 路由组件(函数)写好后需要通过
module. exports
输出出去,然后再在需要调用的js文件顶部通过require
调用后才能使用。**(app.js中的serverHandle不是路由组件,但是注意,函数都需要输出!不输出www.js
中就用不了)** - 千万不要忘记在**终端执行
npm run dev
**!!(否则就不会去监听8000端口) - 设置接口路径时前面前往不要忘记api前面的
/
!
思路
我们先不管接口内容,去把各个接口跑通。blog.js
和user.js
中存放的是路由组件。app.js
用来设置系统比较基础的功能(处理blog
、user
路由)或者参数(获取path
、解析query
)还有返回类型(JSON),还是不涉及业务逻辑的处理。
在blog.js
和user.js
中通过判断以后返回的是对象,所以在app.js
中得到的blogData
也是一个对象,但res.end
返回的需要是字符串,所以需要通过JSON.stringify()
来转换一下,此时注意需要return
来结束。
复习:处理HTTP请求简单示例
为了帮助理解下面被拆分的代码,我们需要先复习一下一个简单的示例。
- 调用 node的**
'http'
模块** - 使用
'http'
模块的createServer()
来创建一个server
服务端 createServer()
接收一个函数作为参数,该函数接收2个参数,分别是 浏览器向server
服务端**发送的请求req
和 服务端返回给浏览器的响应res
**。- 其中**
res.end()
中需要返回字符串**,该字符串会显示在页面上。 - 让**
server
服务端监听8000端口**,好让浏览器访问http://localhost:8000/
时可以访问到服务端。
在下面的代码中,这一部分的“开启 Server,监听8000端口”写在www.js
中,createServer()
的回调函数(参数)被写在app.js
中。
代码
www.js
开启 Server,监听8000端口:
1 | const http = require("http") |
app.js
处理逻辑:
1 | // 引用路由组件 |
blog.js
5个路由接口:
1 | //接收 浏览器的请求req,服务端的响应res |
user.js
一个登录接口:
1 | const handleUserRouter = (req, res) => { |
测试结果
直接输入http://localhost:8000/
,未命中设定好的任何一个路由,触发设定好的404:
输入http://localhost:8000/api/blog/list?author=hlz&keyword=hahaha
,命中其中一个接口:
打开postman测试post请求,如下图所示:
再测试一个user的登录接口:
简化
把两个路由blog.js
和user.js
中都用得到的path
参数放到app.js
中一次性获取。(因为req和res都会传给路由组件,所以可以在app.js
中获取path
参数以后挂载在req/res上传给用的到的路由组件)
原始代码:
简化后:
补充:constructor构造方法
constructor 是一种用于创建和初始化class创建的对象的特殊方法。
在一个构造方法中可以使用super关键字来调用一个父类的构造方法。
MDN中关于constructor构造方法的解释和例子
补充:JSON.parse()
JSON.parse()
方法用于将一个 JSON 字符串转换为对象。
可参考笔记“JSON方法”
4-7 开发路由(博客列表路由)
resModel.js
建数据模型
在src
下新建model
文件夹,在该文件夹下新建resModel.js
文件,在该文件**新建一个基类BaseModel
**。
通过基类BaseModel
建两个模型SuccessModel
和ErrorModel
,希望通过这两个模型实现返回格式的统一。
resModel.js
代码解析:
- 4
BaseModel
中,constructor()
传入的**data
是一个对象类型,message
是一个字符串**类型的消息。 - 6-13 兼容,使得
constructor()
可以只传一个字符串,也可以传一个对象和一个字符串。- 假设传入的第一个参数
data
是字符串类型且没有传入第二个参数message
,为使其兼容则需将参数data
赋给BaseModel
的message
,且data
和message
都不要了。 - 注意:
this.message
才是BaseModel
的message
。data
和message
都只是传入的参数。(不清楚可以看MDN中的例子)
- 假设传入的第一个参数
- 14-19 如果传入
constructor()
的是data对象,那就赋值给BaseModel
的data
(this.data
)。message
同理。 - 23 我们需要**设立两个模型
SuccessModel
和ErrorModel
(他们继承BaseModel
**),用于app.js
中 的res.end
。- 26 使用
super
继承父类BaseModel
的data
和message
。super相当于执行了BaseModel
的constructor
,统一将data
和message
传过去放在父类BaseModel
中处理
- 26 使用
- 最后记得输出两个模块(SuccessModel和ErrorModel)。
- 注意:BaseModel中的this指向使用SuccessModel/ErrorModel的对象
- 比如:
router
-blog.js
-handleBlogRouter()
里new了一个SuccessModel
,那么this
就指向在app.js
中调用了handleBlogRouter()
的**,对象blogData
**。所以对象blogData
中就会有SuccessModel
中的data
、message
、errno
。
- 比如:
- 接下来就可以**通过
new SuccessModel()
或new ErrorModel()
来new一个对象了。(数据等就可以通过参数传入)**(可参考MDN理解)
resModel.js
:
1 | class BaseModel { |
借助这2个模型我们希望实现返回格式的统一(非实际使用,只是在app.js中做效果展示):
app.js
解析公共参数
所有公共的参数都放到app.js
中进行解析(比如query的解析):
(req.query
得到的效果可以参考这个例子)
1.app.js
中顶部增添:
1 | //引用js原生模块querystring |
2.app.js
的serverHandle
中增添:
1 | //解析query,parse方法用于将一个 JSON 字符串转换为对象 |
新建controller
文件夹
- 在
src
下新建controller
文件夹,在该文件夹下新建blog.js
文件。controller
文件夹中的js文件类似于router
中的文件。
blog.js
文件中先返回假数据(格式是正确的),此时虽然author和keyword没有使用,但我们假装他使用了并返回了很多的元素(博客)。- 没连接数据库,先返回假数据
- 由于接下来还要建很多个函数,所以返回(输出)的是对象(方便增加返回的函数)。
controller
文件夹内的blog.js
:
1 | const getList = (author, keyword) => { |
其中,createTime
可以在网页上获取一个来使用:
router
中引用controller
- 可以在
router
文件夹内的blog.js
文件中通过解构赋值的方式引用在controller
文件夹内的blog.js
文件中定义的**getList
函数**。(至于为什么使用解构赋值可参考笔记“Node.js函数引用与解构赋值”) - 想要调用
getList
就必须传入参数author, keyword
,而author, keyword
是从queryString
中来的,所以我们可以在app.js
中统一获取到req.query
里- 注意:
req.query
得到的是一个JSON对象,如果我们访问http://localhost:8000/api/blog/list?author=hlz&keyword=hahaha
,则这个对象中有author
属性和keyword
属性,可参考这个例子。
- 注意:
- 通过
getList()
拿到的数据listData
作为参数data **传入SuccessModel
**。SuccessModel
中的**this
指向blogData
对象。经过SuccessModel
相当于给blogData
对象添加属性data
以及属性值listData
和属性errno
**。- 注意:BaseModel中的this指向使用SuccessModel/ErrorModel的对象
- 也就是说:
router
-blog.js
-handleBlogRouter()
里new了一个SuccessModel
对象,那么this
就指向在app.js
中调用了handleBlogRouter()
的对象blogData
。所以**对象blogData
中就会有SuccessModel
中的data
、message
、errno
**。
- 通过
SuccessModel
合成后的数据在app.js
中作为blogData
对象的属性 通过res.end()
显示在页面上。
router
文件夹内的blog.js
顶部:
1 | //通过解构赋值的方式引用controller/blog.js中的getList函数 |
将router
文件夹内的blog.js
中“获取博客列表”的部分进行修改:
1 | //获取博客列表,api前面千万不要漏掉 / !!! |
测试获取博客列表的接口通过假数据显示已经跑通了:
捋清思路(系统架构设计的四层)
系统架构设计的四层抽象:
第一层: www.js [开启 Server]
第二层:app.js [通信设置层]
第三层:router文件夹 [业务逻辑层]
第四层:controller文件夹 [数据层]
一开始进入项目执行的是bin
文件夹下的**www.js
,这里面只是createServer
的逻辑,端口连通什么的,和我们的业务逻辑没有关系。
第二层:app.js
,用来设置系统比较基础的功能(处理blog
、user
路由)或者参数(获取path
、解析query
)还有返回类型(JSON),还是不涉及业务逻辑的处理。
第三层:router文件夹下的两个路由文件** blog.js
、user.js
,稍微涉及逻辑层,但只管路由。匹配到路由(接口)以后会去处理一些数据,然后(通过SuccessModel)会给你返回一个正确的格式。至于这些数据是怎么去匹配的,怎么去筛选的,是正确的还是错误的他不管,他只管和路由(接口)有关的。
第四层:controller.js
是最关心数据的层次,他没有res
、req
、path
、query
这些东西,只是对传入的数据进行计算处理再返回。(我们目前还没有计算,接下来会有)。至于数据返回后是怎么处理的controller.js
是不管的。
一层层拆分是一个由最基础的http服务向逻辑层转变的过程。
我们一开始先在www.js
上创建服务器server并监听8000端口,这个服务器做的事情放到app.js
的serverHandle中。
然后在app.js
中将HTTP请求req和响应res传到路由组件(router
文件夹),并设置一些 公共参数 通过req/res传给路由组件以供使用。
在router
文件夹下的路由组件blog.js
、user.js
中,根据app.js
传过来的req、res匹配对应的接口并将从controller.js
中得到的数据进行一些格式的处理。最终返回显示在页面上。
在controller.js
中对数据进行计算处理再返回。