【笔记】Vue Element+Node.js开发企业通用管理后台系统——用户登录(下)(搭建https、拦截器、跨域、数据库)
文章目录
一、后端 API 处理流程
二、搭建 https 服务
首先需要将 https
证书拷贝到 node
项目中,然后添加下列代码:
const fs = require('fs')
const https = require('https')
const privateKey = fs.readFileSync('https/book_youbaobao_xyz.key', 'utf8')
const certificate = fs.readFileSync('https/book_youbaobao_xyz.pem', 'utf8')
const credentials = { key: privateKey, cert: certificate }
const httpsServer = https.createServer(credentials, app)
const SSLPORT = 18082
httpsServer.listen(SSLPORT, function() {
console.log('HTTPS Server is running on: https://localhost:%s', SSLPORT)
})
启动 https
服务需要证书对象 credentials
,包含了私钥和证书,重新启动 node
服务:
node app.js
在浏览器中输入:
https://book.aimooc.top:18082
可以看到:
说明 https
服务启动成功
三、创建 /user/login API
在 router/user.js
中填入以下代码:
router.post('/login', function(req, res, next) {
console.log('/user/login', req.body)
res.json({
code: 0,
msg: '登录成功'
})
})
在控制台测试:
$ curl https://book.aimooc.top:18082/user/login -X POST -d "username=sam&password=123456"
{"code":0,"msg":"登录成功"}
上述命令可以简写为:
curl https://book.aimooc.top:18082/user/login -d "username=sam&password=123456"
这里通过 req.body
获取 POST
请求中的参数,但是没有获取成功,需要通过 body-parser
中间件来解决这个问题:
npm i -S body-parser
在 app.js 中加入:
const bodyParser = require('body-parser')
// 创建 express 应用
const app = express()
app.use(bodyParser.urlencoded({ extended: true }))
app.use(bodyParser.json())
app.use('/', router)
这样body
里的参数就被正确解析出来了:
TIP
关于 body-parser 的实现原理与细节可以参考这篇文档,说得非常明白:https://juejin.im/post/59222c5d2f301e006b1616ae
返回前端使用登录按钮请求登录接口,发现控制台报错:
Access to XMLHttpRequest at 'https://book.youbaobao.xyz:18082/user/login' from origin 'http://localhost:9527' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
这是由于前端部署在 http://localhost:9527
而后端部署在 https://book.aimooc.top:18082
,所以导致了跨域错误,我们需要在 node
服务中添加跨域中间件 cors
:
npm i -S cors
然后修改 app.js:
const cors = require('cors')
// ...
app.use(cors())
再次请求即可成功,这里我们在 Network
中会发现发起了两次 https
请求,这是因为由于触发跨域,所以会首先进行 OPTIONS
请求,判断服务端是否允许跨域请求,如果允许才能实际进行请求,这是由浏览器决定的
TIP:
- 注意,这个代码一定要,写在注册路由的前面。此模块也可以,当做路由中间件,指定某一个,或者某一部分路由,拥有跨域功能。
- 关于为什么要发起 OPTIONS 请求,大家可以参考这篇文档:> https://juejin.im/post/5cb3eedcf265da038f7734c4
在前端登录时,检查请求信息发现如下:
需要修改vue-element-admin\src\api\user.js
为:
此时就正常了:
CORS
:是一个W3C标准,全称"跨域资源共享"(Cross-origin resource sharing)。CORS
需要浏览器和服务器同时支持,才可以实现跨域请求,目前几乎所有浏览器都支持CORS
,IE不能低于IE10。CORS
的整个过程都由浏览器自动完成,前端无需做任何设置,跟平时发送ajax请求并无差异。so,实现CORS
的关键在于服务器,只要服务器实现CORS
接口,就可以实现跨域通信。详细可见:
四、响应结果封装
在 /user/login
我们看到返回值是:
res.json({
code: 0,
msg: '登录成功'
})
之后我们还要定义错误返回值,但如果每个接口都编写以上代码就显得非常冗余,而且不易维护,比如我们要将 code
默认值从 0 改为 1,就要修改每个接口,所以我们创建一个 Result
类来解决这个问题
创建 /models/Result.js
文件:
const {
CODE_ERROR,
CODE_SUCCESS
} = require('../utils/constant')
class Result {
constructor(data, msg = '操作成功', options) {
this.data = null
if (arguments.length === 0) {
this.msg = '操作成功'
} else if (arguments.length === 1) {
this.msg = data
} else {
this.data = data
this.msg = msg
if (options) {
this.options = options
}
}
}
createResult() {
if (!this.code) {
this.code = CODE_SUCCESS
}
let base = {
code: this.code,
msg: this.msg
}
if (this.data) {
base.data = this.data
}
if (this.options) {
base = { ...base, ...this.options }
}
console.log(base)
return base
}
json(res) {
res.json(this.createResult())
}
success(res) {
this.code = CODE_SUCCESS
this.json(res)
}
fail(res) {
this.code = CODE_ERROR
this.json(res)
}
}
module.exports = Result
修改 /utils/constant.js
:
module.exports = {
CODE_ERROR: -1,
CODE_SUCCESS: 0
}
Result
使用了 ES6 的 Class
,使用方法如下:
// 调用成功时
new Result().success(res)
new Result('登录成功').success(res)
// 调用成功时,包含参数
new Result({ token }, '登录成功').success(res)
// 调用失败时
new Result('用户名或密码不存在').fail(res)
有了 Result
类后,我们可以将登录 API (router\user.js
)改为:
router.post('/login', function(req, res, next) {
const { username, password } = req.body
if (username === 'admin' && password === '123456') {
new Result('登录成功').success(res)
} else {
new Result('登录失败').fail(res)
}
})
如果在响应前抛出 Error,此时 Error 将被我们自定义的异常处理捕获,并返回 500 至前端(
throw new Error('ERROR...')
)
五、登录用户数据库查询
响应过程封装完毕后,我们需要在数据库中查询用户信息来验证用户名和密码是否准确,首先需要知道一些在node中使用mysql的基本操作:
1.安装
安装 mysql 库:
cnpm i -S mysql
2.配置
创建 db 目录,新建两个文件:
- config.js
module.exports = {
host: 'localhost',
user: 'root',
password: '123456',
database: 'book'
}
- index.js
const mysql = require('mysql')
const config = require('./config')
// 连接
function connect() {
return mysql.createConnection({
host: config.host,
user: config.user,
password: config.password,
database: config.database,
multipleStatements: true
})
}
// 查询
function querySql(sql) {
const conn = connect()
return new Promise((resolve, reject) => {
try {
conn.query(sql, (err, results) => {
if (err) {
// debug && console.log('查询失败,原因:' + JSON.stringify(err))
reject(err)
} else {
// debug && console.log('查询成功', JSON.stringify(results))
resolve(results)
}
})
} catch (e) {
reject(e)
} finally {
conn.end() // 注意一定要加,否则会造成内存泄漏
}
})
}
module.exports = {
querySql
}
multipleStatements:允许每条 mysql 语句有多条查询.使用它时要非常注意,因为它很容易引起 sql 注入(默认:false)
在router\user.js
中添加如下语句测试连接:
const { querySql } = require('../db') // new
const router = express.Router()
router.post('/login', function(req, res, next) {
console.log('req.body', req.body)
const { username, password } = req.body
querySql('select * from admin_user').then(res => { console.log(res) }) // new
if (username === 'admin' && password === '111111') {
new Result('登录成功').success(res)
} else {
new Result('登陆失败').fail(res)
}
})
登录,看到node
控制台显示查询结果即成功。
我们在 constant.js
创建一个 debug
参数控制日志打印:
在utils\constant.js
中新增debug: true
修改db\index.js
:
const mysql = require('mysql')
const config = require('./config')
const { debug } = require('../utils/constant') // new
// 连接
function connect() {
return mysql.createConnection({
host: config.host,
user: config.user,
password: config.password,
database: config.database,
multipleStatements: true
})
}
// 查询
function querySql(sql) {
const conn = connect()
debug && console.log(sql) // new
return new Promise((resolve, reject) => {
try {
conn.query(sql, (err, results) => {
if (err) {
debug && console.log('查询失败,原因:' + JSON.stringify(err)) // new
reject(err)
} else {
debug && console.log('查询成功', JSON.stringify(results)) // new
resolve(results)
}
})
} catch (e) {
reject(e)
} finally {
conn.end() // 注意一定要加,否则会造成内存泄漏
}
})
}
module.exports = {
querySql
}
这时如有查询错误就会在控制台显示,方便debug
测试完毕,移除router\user.js
中的:
const { querySql } = require('../db')
querySql('select * from admin_user').then(res => { console.log(res) })
这里我们需要基于 mysql
查询库封装一层 service
,用来协调业务逻辑和数据库查询,我们不希望直接把业务逻辑写在 router
中,创建 /service/user.js
:
const { querySql } = require('../db')
function login(username, password) {
const sql = `select * from admin_user where username='${username}' and password='${password}'`
return querySql(sql)
}
module.exports = {
login
}
改造 /user/login
API:
const express = require('express')
const Result = require('../models/Result')
const { login } = require('../services/user')
const router = express.Router()
router.post('/login', function(req, res, next) {
const { username, password } = req.body
login(username, password).then(user => {
if (!user || user.length === 0) {
new Result('登录失败').fail(res)
} else {
new Result('登录成功').success(res)
}
})
})
...
此时即使我们输入正确的用户名和密码仍然无法登录,这是因为密码采用了 MD5 + SALT 加密,所以我们需要对密码进行对等加密,才能查询成功。
在 /utils/constant.js
中加入 SALT:
module.exports = {
// ...
PWD_SALT: 'admin_imooc_node',
}
安装 crypto
库:
cnpm i -S crypto
新建/utils/index.js
:
const crypto = require('crypto')
function md5(s) {
// 注意参数需要为 String 类型,否则会出错
return crypto.createHash('md5')
.update(String(s)).digest('hex');
}
module.exports = {
md5
}
修改/user/login
:
const express = require('express')
const Result = require('../models/Result')
const { login } = require('../services/user')
const { md5 } = require('../utils/index') // new
const { PWD_SALT } = require('../utils/constant') // new
const router = express.Router()
router.post('/login', function(req, res, next) {
let { username, password } = req.body
password = md5(`${password}${PWD_SALT}`) // new
login(username, password).then(user => {
if (!user || user.length === 0) {
new Result('登录失败').fail(res)
} else {
new Result('登录成功').success(res)
}
})
})
再次输入正确的用户名和密码,查询成功:
六、express-validator
express-validator 是一个功能强大的表单验证器,它是 validator.js 的中间件
源码地址:https://github.com/express-validator/express-validator
使用 express-validator 可以简化 POST 请求的参数验证,使用方法如下:
安装
cnpm i -S express-validator
验证
修改/user/login
:
const express = require('express')
const Result = require('../models/Result')
const { login } = require('../services/user')
const { md5 } = require('../utils/index')
const { PWD_SALT } = require('../utils/constant')
const { body, validationResult } = require('express-validator')
const boom = require('boom')
const router = express.Router()
router.post(
'/login',
[
body('username').isString().withMessage('username类型不正确'),
body('password').isString().withMessage('password类型不正确')
],
function(req, res, next) {
const err = validationResult(req)
if (!err.isEmpty()) {
const [{ msg }] = err.errors
next(boom.badRequest(msg)) // 400错误
} else {
const username = req.body.username
const password = md5(`${req.body.password}${PWD_SALT}`)
login(username, password).then(user => {
if (!user || user.length === 0) {
new Result('登录失败').fail(res)
} else {
new Result('登录成功').success(res)
}
})
}
})
express-validator
使用技巧:
- 在
router.post
方法的第二个参数中使用body
方法判断参数类型,并指定出错时的提示信息 - 使用
const err = validationResult(req)
获取错误信息,err.errors
是一个数组,包含所有错误信息,如果err.errors
为空则表示校验成功,没有参数错误 - 如果发现错误我们可以使用
next(boom.badRequest(msg))
抛出异常,交给我们自定义的异常处理方法进行处理
boom是一个兼容 HTTP 的错误对象,他提供了一些标准的 HTTP 错误,比如 400(参数错误)等。
相关文章
- Vue.js—Difference between v-model and v-bind
- vue - check-versions.js for child_process
- 【Vue】Vue-cli(脚手架)中的main.js内容解读
- 【Javascript/Vue】如何解决js中超链接跳转到新的页面不被浏览器拦截?(已解决,代码实例,亲测有效)
- 一文带你理解vue创建一个后台管理系统流程(Vue+Element)
- vue.js-详解三大流行框架VUE_快速进阶前端大咖-Vue基础
- vue插件
- vue的地图插件amap
- 基于VUE实现的优鲜食物的移动端购物网站【100010164】
- vue-router实现SPA购物APP基本功能
- Vue.js中this.$nextTick()的使用
- vue--组件动画效果--淡入淡出--位移
- JS教程之使用 Pinia、Electron 和 Quasar 构建 Vue 3 桌面应用程序
- 在CSS中使用JS变量Vue项目
- vue 中 直接操作 cookie 及 如何使用工具 js-cookie
- Vue之computed计算属性
- Vue开发实例(14)之Vue状态管理store
- Vue实现省市区三级联动,下拉框,简单易懂
- ASP.NET MVC+Vue.js实现联系人管理