问题:
- 你是否知道npm的概念和作用?
- 你是否知道模块化的概念,和node项目中的模块化?
- 搭建node新项目时,为实现某一基本功能,你是否总是在网上各种查找如何安装对应的模块包和相关配置?
如果这些问题在你心中都有标准答案,那你就可以去看别的文章啦~
如果你还有些一知半解,欢迎看官们评阅我的文章!
前言
最近自己编写了一个后台管理系统,选用了 node.js 和 vue 相关框架和技术。也算是收获了不少知识和经验,因此,我来写下这篇文章,向大家分享一些关于node.js的核心知识,并在最后手把手教你们快速搭建并配置一个node新项目(涉及如何配置express、joi、jwt、mysql、cors)。
Node.js是什么?
首先,想必大家都使用过JavaScript吧!
你们知道为什么JavaScript可以操作浏览器中的DOM和BOM吗?
每个浏览器都内置了 DOM、BOM 这样的 API 函数,因此,浏览器中的 JavaScript 才可以调用它们。
那么,为什么浏览器可以解析JavaScript语言呢?
不同的浏览器使用了不同的 JavaScript 解析引擎,用来解析我们编写JavaScript
其中,Chrome 浏览器的 V8 解析引擎性能最好
这里我在网上找了张图片,很生动地解释了我上面的回答:
Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine.
Node.js 是一个基于 Chrome V8 引擎的 JavaScript 后端运行环境
简而言之,就是一个使用JavaScript写后端的一个技术
它仅仅提供了一些基础的功能和 API。但是,基于这些基础功能和API,产生了许多强大框架,
如:
基于 Express 框架(http://www.expressjs.com.cn/),可以快速构建一个 Web应用
基于 Electron 框架(https://electronjs.org/),可以构建跨平台的桌面应用
基于 restify 框架(http://restify.com/),可以快速构建 API 接口项目
只要你会JavaScript,你就可以用node.js写后端,Springboot SpringMVC Mybatis能做的,你都可以使用node.js来实现!
模块化
普遍概念:
模块化是指解决一个复杂问题时,自顶向下逐层把系统划分成若干模块的过程。对于整个系统来说,模块是可组合、分解和更换的单元。
编程领域的模块化:
编程领域中的模块化,就是遵守固定的规则,把一个大文件拆成独立并互相依赖的多个小模块。
作用:
提高了代码的复用性
提高了代码的可维护性
可以实现按需加载
那么Node.js中是怎样实现模块化的呢?
⚫ 内置模块(由 Node.js 官方提供,例如 fs、path、http 等)
⚫ 自定义模块(用户创建的每个 .js 文件)
⚫ 第三方模块(由第三方开发出来的模块,使用前需要先下载),又叫做包(重要)
包, 即第三方模块是基于内置模块封装出来的,提供了更高级、更方便的 API,极大的提高了开发效率。
而这些模块通常使用 node.js中的 require() 方法加载进行使用
代码语言:javascript复制const fs = require('fs') //加载内置的 fs 模块,用于读写文件
const router = require(./userRouter.js) //加载用户自定义的js文件
const express = require('express') //加载第三方模块express,用于创建和配置服务器实例
每个模块文件都通过module.exports或exports来将模块内的成员共享出去,供外界使用。
外界用 require() 方法导入自定义模块时,得到的就是 module.exports 所指向的对象
这里注意区分module.exports和exports, exports是为了简化前者的编写而生的
默认情况下,exports 和 module.exports 指向同一个对象。但最终共享的结果,还是以 module.exports 指向的对象为准。
这里让我们看看CommonJS的规定:
CommonJS 规定了模块的特性和各模块之间如何相互依赖
① 每个模块内部,module 变量代表当前模块。
② module 变量是一个对象,它的 exports 属性(即 module.exports)是对外的接口。
③ 加载某个模块,其实是加载该模块的 module.exports 属性。require() 方法用于加载模块
npm(Node Package Manager)
概念:
NPM是随同NodeJS一起安装的包管理工具,能解决NodeJS代码部署上的很多问题,常见的使用场景有以下几种:
1.允许用户从NPM服务器下载别人编写的第三方包到本地使用。
2.允许用户从NPM服务器下载并安装别人编写的命令行程序到本地使用。
3.允许用户将自己编写的包或命令行程序上传到NPM服务器供别人使用。
npm 规定,在项目根目录中,必须提供一个叫做 package.json 的包管理配置文件。用来记录与项目有关的一些配置
信息。例如:
⚫ 项目的名称、版本号、描述等
⚫ 项目中都用到了哪些包
⚫ 哪些包只在开发期间会用到
⚫ 那些包在开发和部署时都需要用到
如何快速创建 package.json?
代码语言:javascript复制//对于项目起始的空文件夹
npm init -y
//运行 npm install 命令安装包的时候, npm会自动把包的名称和版本号,记录到 package.json 中
注意:上述命令只能在英文的目录下成功运行!所以项目的根目录名字,不能有英文,也不能有空格
该文件中的dependencies结点,记录着项目安装的所有包和版本号
拿到别人的项目时,如果项目文件夹中没有项目需要的包(因为包存储文件夹node_modules文件夹过大,通常项目编写者不会把它上传到github等网站),你可以使用
代码语言:javascript复制npm i
安装package.json的dependencies结点下的所有包
快速搭建配置一个NodeJs的新项目
可以参考我的项目文件夹结构
1. 创建项目
1.1 新建 server
文件夹,作为项目根目录,并在根目录中运行如下的命令,初始化包管理配置文件:
代码语言:javascript复制npm init -y
然后你的项目中就会出现pakage.json文件了
1.2 安装特定版本的 express
:
代码语言:javascript复制npm i express@4.17.1
web服务器的一个流行框架,用来创建和配置服务器实例
1.3 在项目根目录中新建 app.js
作为整个项目的入口文件,并初始化如下的代码:
代码语言:javascript复制// 导入 express 模块
const express = require('express')
// 创建 express 的服务器实例
const app = express()
// 之后的其他配置都写在这里
// 调用 app.listen 方法,指定端口号并启动web服务器
app.listen(8888, function () {
console.log('server is running at http://127.0.0.1:8888')
})
并在package.json中修改项目的入口为app.js
2. 配置 cors 跨域
2.1 安装 cors
中间件:
代码语言:javascript复制npm i cors@2.8.5
2.2 在 app.js
中导入并配置 cors
中间件:
代码语言:javascript复制// 导入 cors 中间件
const cors = require('cors')
// 将 cors 注册为全局中间件,允许跨域请求
app.use(cors())
3. 配置解析表单数据的中间件和路由
3.1 配置解析 application/x-www-form-urlencoded
格式的表单数据的中间件,不然服务器无法解析post请求中的请求体body里为表单数据格式的参数
代码语言:javascript复制app.use(express.urlencoded({ extended: false }))
3.2 初始化路由相关的文件夹
- 在项目根目录中,新建
router
文件夹,用来存放所有的路由
模块 路由模块中,只存放客户端的请求与处理函数之间的映射关系 - 在项目根目录中,新建
router_handler
文件夹,用来存放所有的路由处理函数模块
路由处理函数模块中,专门负责存放每个路由对应的处理函数
类似SSM框架中的service接口和serviceImpl类的关系
3.3 初始化用户路由模块
在 router
文件夹中,新建 user.js
文件(举个例子),作为用户的路由模块,并初始化代码格式如下:
代码语言:javascript复制const express = require('express')
const router = express.Router()
// 导入用户路由处理函数模块
const userHandler = require('../router_handler/user')
// 处理登录请求的映射关系
router.post('/login', userHandler.login)
module.exports = router
在 /router_handler/user.js
中,使用 exports
对象,分别向外共享对应的 路由处理函数
:
代码语言:javascript复制/**
* 在这里定义和用户相关的路由处理函数,供 /router/user.js 模块进行调用
*/
// 登录请求的处理函数
exports.login = (req, res) => {
res.send('login OK')
}
在 app.js
中,导入并使用 用户路由模块
:
代码语言:javascript复制// 导入并注册用户路由模块
const userRouter = require('./router/user')
app.use('/api', userRouter)
4. 安装并配置 mysql
这个第三方模块,来连接和操作 MySQL 数据库
4.1 安装 mysql
模块:
代码语言:javascript复制npm i mysql@2.18.1
4.2 在项目根目录中新建 /db/index.js
文件,在此自定义模块中创建数据库的连接对象:
代码语言:javascript复制// 导入 mysql 模块
const mysql = require('mysql')
// 创建数据库连接对象
const db = mysql.createPool({
host: '127.0.0.1',
user: 'root',
password: 'root',
database: 'db_name',
})
// 向外共享 db 数据库连接对象
module.exports = db
5. 配置bcryptjs
在当前项目中,使用 bcryptjs
对用户密码进行加密,
优点:
加密之后的密码,无法被逆向破解
同一明文密码多次加密,得到的加密结果各不相同,保证了安全性
5.1 运行如下命令,安装指定版本的 bcryptjs
:
代码语言:javascript复制npm i bcryptjs@2.4.3
5.2 在 /router_handler/user.js
中,导入 bcryptjs
:
代码语言:javascript复制const bcrypt = require('bcryptjs')
5.3 若有注册功能,可以在注册用户的处理函数中,确认用户名可用之后,调用 bcrypt.hashSync(明文密码, 随机盐的长度)
方法,对用户的密码进行加密处理:
代码语言:javascript复制// 对用户的密码,进行 bcrype 加密,返回值是加密之后的密码字符串
userinfo.password = bcrypt.hashSync(userinfo.password, 10)
// 拿着用户输入的密码,和数据库中存储的密码进行对比
const compareResult = bcrypt.compareSync(用户输入的密码, 数据库中加密的密码)
表单验证的原则:前端验证为辅,后端验证为主,后端永远不要相信前端提交过来的任何内容
6. 配置表单验证模块
使用 if...else...
的形式对数据合法性进行验证,效率低、出错率高、又不方便维护。因此,可以选择使用第三方数据验证模块,来降低出错率、提高验证的效率与可维护性,让我们可以把更多的精力放在核心业务逻辑的处理上。
6.1 安装 joi
包,为表单中携带的每个数据项,定义验证规则:
代码语言:javascript复制npm install joi
6.2 安装 @escook/express-joi
中间件,来实现自动对表单数据进行验证的功能:
代码语言:javascript复制npm i @escook/express-joi
6.3 新建 /schema/user.js
用户信息验证规则模块,并初始化代码如下:
代码语言:javascript复制const joi = require('joi')
/**
* string() 值必须是字符串
* alphanum() 值只能是包含 a-zA-Z0-9 的字符串
* min(length) 最小长度
* max(length) 最大长度
* required() 值是必填项,不能为 undefined
* pattern(正则表达式) 值必须符合正则表达式的规则
*/
// 用户名的验证规则
const username = joi
.string()
.alphanum()
.min(1)
.max(10)
.required()
// 密码的验证规则
const password = joi
.string()
.pattern(/^[S]{6,12}$/)
.required()
// 向外共享登录表单的验证规则对象
exports.reg_login_schema = {
// 表示需要对 req.body 中的数据进行验证
body: {
username,
password,
},
}
6.4 修改 /router/user.js
中的代码如下:
代码语言:javascript复制const express = require('express')
const router = express.Router()
// 导入用户路由处理函数模块
const userHandler = require('../router_handler/user')
// 1. 导入验证表单数据的中间件
const expressJoi = require('@escook/express-joi')
// 2. 导入需要的验证规则对象
const { reg_login_schema } = require('../schema/user')
// 登录
// 3. 在用户登录的路由中,声明局部中间件,对当前请求中携带的数据进行验证
// 3.1 数据验证通过后,会把这次请求流转给后面的路由处理函数
// 3.2 数据验证失败后,终止后续代码的执行,并抛出一个全局的 Error 错误
router.post('/login', reg_login_schema, userHandler.login)
module.exports = router
7. 生成 JWT 的 Token 字符串
在生成 Token 字符串的时候,一定要剔除 密码 和 头像 等隐私的值,以保证用户信息安全
7.1 通过 ES6 的高级语法,快速剔除 密码
的值:
代码语言:javascript复制// 剔除完毕之后,user 中只保留了用户的 id, username, nickname, email 这四个属性的值
const user = { ...results[0], password: '' }
7.2 安装生成 Token 字符串的包:
代码语言:javascript复制npm i jsonwebtoken@8.5.1
7.3 在 /router_handler/user.js
模块的头部区域,导入 jsonwebtoken
包:
代码语言:javascript复制// 用这个包来生成 Token 字符串
const jwt = require('jsonwebtoken')
7.4 创建 config.js
文件,并向外共享 加密 和 还原 Token 的 jwtSecretKey
字符串:
代码语言:javascript复制module.exports = {
jwtSecretKey: 'CodeGoat24 ^_^',
}
7.5 将用户信息对象加密成 Token 字符串:
代码语言:javascript复制// 导入配置文件
const config = require('../config')
// 生成 Token 字符串
const tokenStr = jwt.sign(user, config.jwtSecretKey, {
expiresIn: '10h', // token 有效期为 10 个小时
})
7.6 将生成的 Token 字符串响应给客户端:
代码语言:javascript复制res.send({
status: 0,
message: '登录成功!',
// 为了方便客户端使用 Token,在服务器端直接拼接上 Bearer 的前缀
token: 'Bearer ' tokenStr,
})
8 配置解析 Token 的中间件
8.1 运行如下的命令,安装解析 Token 的中间件:
代码语言:javascript复制npm i express-jwt@5.3.3
8.2 在 app.js
中注册路由之前,配置解析 Token 的中间件:
代码语言:javascript复制// 导入配置文件
const config = require('./config')
// 解析 token 的中间件
const expressJWT = require('express-jwt')
// 使用 .unless({ path: [/^/api//] }) 指定哪些接口不需要进行 Token 的身份认证
app.use(expressJWT({ secret: config.jwtSecretKey }).unless({ path: [/^/api//] }))
总结:
看到这里,你是否对NodeJs有了进一步的了解了呢?NodeJs项目虽然在项目搭建阶段会涉及到比较多的配置,但是搭建好之后,在业务逻辑方面的编写就非常方便了,而搭建NodeJs新项目并对一些常用包进行基本配置,跟着我上面的步骤就足够啦!,这边推荐像我上面将路由处理的功能模块分为router文件夹和router_handler文件夹,模仿SSM框架在业务层和持久层的文件夹结构。router 文件夹只存放客户端的请求与处理函数之间的映射关系,router_handler 文件夹专门负责存放每个路由对应的处理函数,这样路由功能的目录结构会更加清晰!
如果上述分享有错误之处,欢迎各位在评论区指正!
之后我还会出力扣算法和前后端技术的相关文章,欢迎大家关注支持!