vue-fund-manage记录

依赖总结

后端部分

1
2
3
4
5
6
7
8
9
10
"dependencies": {
"bcrypt": "^3.0.6", // 进行密码的加密
"body-parser": "^1.19.0", // post请求
"concurrently": "^4.1.1", // 前后端连载
"express": "^4.17.1", // express框架
"jsonwebtoken": "^8.5.1", // 生成token
"mongoose": "^5.6.9", // mongoose框架
"passport": "^0.4.0", // passport配合passport-jwt实现token验证
"passport-jwt": "^4.0.0"
}

前端部分

1
2
3
4
5
6
7
8
9
"dependencies": {
"axios": "^0.19.0", // 进行数据请求
"core-js": "^2.6.5",
"element-ui": "^2.11.1", // elementUI
"jwt-decode": "^2.2.0", // token的解析
"vue": "^2.6.10",
"vue-router": "^3.0.3",
"vuex": "^3.0.1"
}

token

生成token

https://www.npmjs.com/package/jsonwebtoken

后端代码 users.js 登录成功部分节选

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const jwt = require('jsonwebtoken');
const keys = require('../../config/keys.js'); // "secret"
if (isMatch) {
const rule = {
id: user.id,
name: user.name,
identity: user.identity
};
// 规则 加密名字 过期时间 箭头函数
jwt.sign(rule, keys.secretOrKey, {expiresIn: 3600}, (err, token) => {
if(err) throw err;
res.json({
success: true,
token: "bearer " + token
})
})
}

验证token

https://www.npmjs.com/package/passport

https://www.npmjs.com/package/passport-jwt

passport初始化

后端代码 index.js 节选

1
2
3
4
const passport = require('passport');
// passport初始化
app.use(passport.initialize());
require('./config/passport.js')(passport);

后端代码 passport.js 节选

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const JwtStrategy = require('passport-jwt').Strategy,
ExtractJwt = require('passport-jwt').ExtractJwt;
const mongoose = require('mongoose');
const User = mongoose.model('users');
const keys = require('../config/keys.js');

const opts = {}
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
opts.secretOrKey = keys.secretOrKey;

module.exports = passport => {
passport.use(new JwtStrategy(opts, (jwt_payload, done) => {
// console.log(jwt_payload);
User.findById(jwt_payload.id)
.then(user => {
if (user) {
return done(null, user);
}
return done(null, false);
})
.catch(err => console.log(err))
}));
}

进行请求

必须带着 token 才能请求成功。使用 postman 进行测试时要设置 headers 部分的 Authorization 为登录请求后得到的 token 值,在这个项目中,只有登录和注册是不带 token 验证的。

后端代码 profiles.js 节选

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// $route POST api/profiles/add
// @desc 添加信息接口
// @access Private
router.post('/add', passport.authenticate('jwt', { session: false }),(req, res) => {
const profileFields = {};
// console.log(req.user);

if (req.body.type) profileFields.type = req.body.type;
if (req.body.describe) profileFields.describe = req.body.describe;
if (req.body.income) profileFields.income = req.body.income;
if (req.body.expend) profileFields.expend = req.body.expend;
if (req.body.cash) profileFields.cash = req.body.cash;
if (req.body.remark) profileFields.remark = req.body.remark;

new Profile(profileFields)
.save()
.then(profile => {
res.json(profile)
})
})

后端代码 users.js 节选

前端部分并没有使用 current 这个接口,这个接口只是为了了解 token ,特别是下面 req.user 部分,个人觉得 req.user 部分就代表 token 验证成功,表示谁去请求。

1
2
3
4
5
6
7
8
9
10
11
12
// $route GET api/users/current
// @desc return current user
// @access Private
router.get("/current", passport.authenticate('jwt', { session: false }), (req, res) => {
// console.log(req.user.identity);
res.json({
id: req.user.id,
name: req.user.name,
email: req.user.email,
identity: req.user.identity,
});
})

请求拦截响应拦截

前端代码中 http.js 节选

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 请求拦截
axios.interceptors.request.use(config => {
// 加载动画
startLoading();
if (localStorage.eleToken) {
// 设置统一的请求header
config.headers.Authorization = localStorage.eleToken;
}
return config;
}, error => {
return Promise.reject(error);
})

// 响应拦截
axios.interceptors.response.use((response) => {
// 结束加载动画
endLoading();
return response;
}, (error) => {
endLoading();
Message.error(error.response.data);

// 获取错误状态码
const { status } = error.response;
if (status === 401) {
Message.error('token失效,请重新登录');
// 清除token
localStorage.removeItem('eleToken');
// 跳转到的登录页面
router.push('login');
}
return Promise.reject(error);
});

vuex

前端部分 store.js 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const types = {
SET_AUTHENTICATED: 'SET_AUTHENTICATED',
SET_USER: 'SET_USER'
}

const state = {
isAuthenticated: false,
user: {}
}

const getters = {
isAuthenticated: state => state.isAuthenticated,
user: state => state.user
}

const mutations = {
[types.SET_AUTHENTICATED](state, isAuthenticated) {
if (isAuthenticated) state.isAuthenticated = isAuthenticated;
else state.isAuthenticated = false;
},

[types.SET_USER](state, user) {
if (user) state.user = user;
else state.user = {};
}
}

const actions = {
setAuthenticated: ({commit}, isAuthenticated) => {
commit(types.SET_AUTHENTICATED, isAuthenticated);
},
setUser: ({commit}, user) => {
commit(types.SET_USER, user);
},
clearCurrentState: ({commit}) => {
commit(types.SET_AUTHENTICATED, false);
commit(types.SET_USER, null);
}
}

export default new Vuex.Store({
state,
getters,
mutations,
actions
})

前端部分 Login.vue 代码节选

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
this.$axios.post("/api/users/login", this.loginUser)
.then(res => {
// console.log(res);
// token
const { token } = res.data;
// 存储到 ls
localStorage.setItem('eleToken', token);
// 解析token
const decoded = jwt_decode(token);
// console.log(decoded);

// token 存储到vuex中
this.$store.dispatch("setAuthenticated", !this.isEmpty(decoded));
this.$store.dispatch("setUser", decoded);

this.$router.push('/index');
})

总结流程

首先进入 register 或者 login 页面,因为有路由守卫,所以不能直接进入 index 页面,进行注册,然后登录,登录会返回后端生成的 token ,存储 tokenlocalStorage 中,并解析 token 存储到 vuex 中,设置授权 setAuthenticated 为真,setUsertoken 解析后的用户信息,如果登录失败,可能是 token 过期失效,如果退出登录的话,会删除 localStorage 中的 token,并将 vuex 中的存储清除。

进行对数据的增删改查需要带上 token ,在请求拦截与响应拦截中有相关的配置。