前面几章已经实现了项目的基础功能(用户登陆注册,查看博客列表),后面要作的就是功能的丰富和代码优化,因为目前代码量还比较少,因此考虑先进行一些代码的优化整合,方便后续开发。html
相比以前代码进行了以下优化:前端
下面就这三点分别介绍一下个人解决方案。node
以前前端运行是依赖webpack的devServer:webpack
devServer: {
contentBase: path.resolve(__dirname, '../dist'),
historyApiFallback: true,
port: 8080,
inline: true,
hot: true,
host: 'localhost',
proxy: {
'/api': {
target: 'http://localhost:3000',
pathRewrite: { '^/': '' }
}
}
}
复制代码
前端代码运行在8080端口,设置代理3000端口用来请求后台接口。
如今使用webpack-dev-middleware +webpack-hot-middleware (页面热更新),进行启动,这样能够在express中经过webpack.config.js获取到webpack文件,随后将其打包到内存中从而启动前端项目,相似devServer的功能;webpack-hot-middleware用来实现页面热更新,当咱们修改代码后会帮咱们更新前端页面:ios
/client/app.js
<!-- express中 引入所需依赖 -->
const express = require('express')
const webpack = require('webpack')
const webpackDevMiddleware = require('webpack-dev-middleware')
const webpackHotMiddleware = require('webpack-hot-middleware')
const WebpackConfig = require('../build/webpack.dev.js')
const compiler = webpack(WebpackConfig)
<!-- 当运行express服务时使用 webpackDevMiddleware,webpackHotMiddleware -->
app.use(
webpackDevMiddleware(compiler, {
publicPath: '/dist/'
})
)
app.use(webpackHotMiddleware(compiler))
复制代码
这样咱们在启动express后,会同时帮咱们打包前端的代码,因为咱们打包后的目录是dist,输入http://localhost:8080/dist/login.html 后就能够打开对应的登陆页面。git
因为以前使用原生node代码较多,好比:
须要写相应的匹配规则来判断请求是否为接口: github
/client/app.js
// 解析 application/json
app.use(bodyParser.json())
// 解析 application/x-www-form-urlencoded
app.use(bodyParser.urlencoded())
复制代码
同时使用exprss中间件可使咱们的代码不须要加不少if else 这样的代码,代码的逻辑会更清晰。web
在咱们登陆成功后,在后端生成token返回给前端,前端获取到token后保存起来,在每次进行请求的时候将token设置在headers中,后端在请求中获取token ——>校验token是否有效 ———> 经过token获取用户信息。这里我前端请求接口使用axios,使用axios的添加拦截器来设置token。sql
由于前端逻辑简单,先贴前端代码:
登陆接口请求成功后,获取token保存在localStorage中:数据库
const account = document.getElementById('account').value
const password = document.getElementById('password').value
const user = await getAxiosData('login', 'post', { account, password })
// 存储token
setStorage('blog-token', user.token)
复制代码
在axios中添加拦截器,请求拦截器:在请求接口时判断本地是否有token,若是有则设置在headers中 这里设置headers 的属性必定要 authorization,内容必定要 'Bearer ' + token。 config.headers.token = token 这种是不能够的,后端校验会报错(=_= 别问怎么知道的)
// 请求拦截器
axios.interceptors.request.use(
function(config) {
const token = getStorage('blog-token')
if (token) {
config.headers.authorization = 'Bearer ' + token
}
return config
},
function(error) {
return Promise.reject(error)
}
)
复制代码
响应拦截器:判断了接口code为401的状况(token校验失败),这里考虑后期可能会在未登陆作些什么,因此加了响应拦截器
// 响应拦截器
axios.interceptors.response.use(function(response) {
const data = response.data
if (data.code === 401) {
return {
code: 401,
message: '请登陆'
}
}
return data
})
复制代码
在app.js中添加校验token的中间件,同时设置不须要校验的接口:
app.use(
jwt({
secret: secretKey
}).unless({
path: ['/api/user/login', '/api/user/register', '/api/blog/list', /\.ico$/]
})
)
// 校验token
app.use(verifyToken)
// verifyToken:
// 校验token
const verifyToken = function(error, req, res, next) {
const token = getToken(req)
if (token) {
jwt.verify(token, secretKey, (err, decoded) => {
if (err) {
return res.send({
code: 401,
message: err.message
})
} else {
return next()
}
})
}
return res.send({
code: error.status,
message: error.message
})
}
复制代码
当用户登陆接口时,获取用户信息并生成token返回给前端:
getUser: 返回数据库中的用户信息;
getTokenByData:将用户信息转成token。
// 用户登陆
router.post('/api/user/login', async (req, res) => {
try {
const user = await getUser(req.body)
const token = getTokenByData(user)
user.token = token
res.send(new SuccessModel(user))
} catch (err) {
res.send(new ErrorModel(err))
}
})
// getTokenByData
const jwt = require('jsonwebtoken')
// 根据数据返回生成token
const getTokenByData = function(data) {
return jwt.sign({ data }, secretKey, {
expiresIn: 60 * 60 * 24 // 受权时效24小时
})
}
复制代码
当用户登陆后请求其余接口时,经过token获取用户信息,再查找数据库中相关数据:
getToken: 根据req返回token getUserByToken: 根据token返回用户信息
// 获取用户信息
router.get('/api/user/getUser', (req, res) => {
const token = getToken(req)
const user = getUserByToken(token)
if (user.id) {
res.send(new SuccessModel(user))
} else {
res.send({
code: 401,
message: user.message
})
}
})
// getToken: 获取 请求 token
const getToken = (req) => {
let token = ''
if (req.headers.authorization) {
token = req.headers.authorization.split(' ')[1]
}
return token
}
// getUserByToken: 经过token返回用户信息
const getUserByToken = function (token) {
let data
jwt.verify(token, secretKey, (err, decoded) => {
if (!err) {
data = decoded.data
} else {
data = err
}
})
return data
}
复制代码
最后,就是执行sql语句查询数据库后对数据进行一个整合返回给前端:
例如获取用户信息:
const getUser = (data, username) => {
const s = '`password`'
let sql
if (username) {
sql = `select id, username, realname from users where username='${username}';`
} else {
sql = `select id, username, realname from users where username='${data.account}' and ${s}='${data.password}';`
}
return new Promise(async (resolve, reject) => {
const user = await exec(sql)
if (user.length === 0) {
reject('帐号或密码错误')
} else if (user.length === 1) {
resolve(user[0])
} else {
resolve(user)
}
})
}
复制代码
本章介绍了
下一章主要内容:
GitHub地址:戳这里
本项目仅为学习交流使用,若是有小伙伴有更好的建议欢迎提出来你们一块儿讨论,另外感兴趣的小伙伴就点个star吧! ^_^