前言
Todo List项目到这里已经到了非常重要的部分,涉及到用户部分。权限,多人协同操作等,都是非常重要,且复杂的地方,所以这里分为几个章节,一步一步的完成。
本章节主要讲讲用户登录,注册会话部分。
代码请戳:Todo List GitHub代码库
本次《todo list: Vue待办事项任务管理》,分为一下章节,有兴趣的同学可以持续关注。
第二章: 数据动态化处理(localStorage + Vuex),可新增,可编辑
第五章:Node + Express 搭建服务端连接Mysql
第七章:多人协同处理待办事项,权限管理
第八章:完结:线上发布
初步定义8个章节,实际开发中有可能有所增减。
Client端
新增pages/user目录,里面存放用户相关的页面,如登录,注册,用户信息等。
/user/login.vue
<div id="login" v-if="type=='Login'">
<div class="form">
<h1>用户登录</h1>
<div class="c-input">
<label for="">用户名</label>
<input type="text" v-model="username" placeholder="请输入用户名">
</div>
<div class="c-input">
<label for="">密码</label>
<input type="password" v-model="password" placeholder="请输入密码">
</div>
<div class="c-input">
<label for=""> </label>
<input class="btn" type="button" value="登录" @click="login">
</div>
</div>
</div>
<div id="register" v-else>
<div class="form">
<h1>用户注册</h1>
<div class="c-input">
<label for="">用户名</label>
<input type="text" v-model="username" placeholder="请输入用户名">
</div>
<div class="c-input">
<label for="">密码</label>
<input type="password" v-model="password" placeholder="请输入密码">
</div>
<div class="c-input">
<label for="">重复密码</label>
<input type="password" v-model="password2" placeholder="再次输入密码">
</div>
<div class="c-input">
<label for=""> </label>
<input class="btn" type="button" value="注册" @click="register">
</div>
</div>
</div>
对应js方法
login () {
let vm = this
vm.$store.dispatch(types.A_LOGIN, {
username: vm.username,
password: vm.password
}).then((data) => {
vm.$router.push({
name: 'TodoList'
})
}, (err) => {
console.log(err)
})
},
register () {
let vm = this
vm.$store.dispatch(types.A_REGISTER, {
username: vm.username,
password: vm.password,
password2: vm.password2
}).then(() => {
vm.$router.push({
name: 'Login',
query: {
type: 1
}
})
})
}
/store/actions.js
[types.A_REGISTER] ({ commit, dispatch }, params) {
http({
method: 'POST',
url: '/user/register',
json: true,
data: params
}).then(() => {
})
},
[types.A_LOGIN] ({ commit, dispatch }, params) {
return http({
method: 'POST',
url: '/user/login',
json: true,
data: params
}).then((resp) => {
return Promise.resolve(resp)
}, (err) => {
return Promise.reject(err)
})
}
/commons/http.js
// 创建实例时设置配置的默认值
var instance = axios.create({
baseURL: '/api',
headers: {
'Content-Type': 'application/json; charset=UTF-8'
},
timeout: 60000 // 设置超时时间为1分钟
})
// 添加请求拦截器
instance.interceptors.request.use(function (config) {
const headers = config.headers = config.headers || {}
// 在发送请求之前做些什么
let token = localStorage.getItem('x-token')
headers['x-token'] = token
if (config.json) {
headers['Content-Type'] = 'application/json; charset=UTF-8'
delete config.json
}
return config
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error)
})
// 添加响应拦截器
instance.interceptors.response.use(function (response) {
// 对响应数据做点什么
if (response.headers['x-token']) {
localStorage.setItem('x-token', response.headers['x-token'])
}
return response
}, function (error) {
// 对响应错误做点什么
let code = error.response.data.code
switch (code) {
case 1000:
location.href = '/user/login?type=1'
break
}
return Promise.reject(error)
})
请求拦截器里面,我们增加增加头x-token,响应里面我们也获取x-token,同时拦截错误码,如果是1000,表示token失效,未登录,我们跳转到登录页面。
创建实例我们修改了baseURL,主要解决跨域问题,同时我们修改根目录下方的/config/index.js文件。
/config/index.js
修改proxyTable为下列代码,拦截请求所有带’/api’的请求,target为后台接口地址域名,pathRewrite是修改请求接口地址url正则,将/api
修改为空字符串。
proxyTable: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}
}
Server端
/server/sql.js
增加2条SQL,登录查询和注册插入。
module.exports = {
...
USER_LOGIN: "SELECT * FROM user where username = '${username}' and password = '${password}'",
USER_REGISTER: "INSERT INTO user (username, password, ip, date) VALUES('${username}', '${password}', '${ip}', '${date}')"
}
/server/app.js
安装2个模块,用来做用户签名
npm i express-jwt jsonwebtoken -S
...
app.set('superSecret', 'todolist'); // 设置 app 的密码--用来生成签名的密码
/**
* 用户登录
*/
app.post('/user/login', (req, res) => {
const params = req.body;
console.log(params)
if(!params.username){
sendError(res, '用户名称不能为空')
return
}
if(!params.password){
sendError(res, '密码名称不能为空')
return
}
let username = params.username
let password = params.password
let cliendIp = utils.getClientIp(req)
let csql = eval('`'+sql.USER_LOGIN+'`');
console.log('[SQL:]', csql);
query(csql, (err, result, fields) => {
if (err) {
console.log('[SELECT ERROR]:', err.message)
return sendError(res, err.message)
}
result = JSON.stringify(result);
result = JSON.parse(result);
if(!result.length){
return sendError(res, '用户信息错误')
}
result.map(item=>{
delete item.password
})
let user = result[0];
// 创建token
var token = jwt.sign(user, app.get('superSecret'), {
expiresIn : 60*60*24// 授权时效24小时
});
user.token = token;
// 请求头返回
res.header('x-token', token);
res.send(user)
})
})
/**
* 用户注册
*/
app.post('/user/register', (req, res) => {
const params = req.body;
console.log(params)
if(!params.username){
sendError(res, '用户名称不能为空')
return
}
if(!params.password){
sendError(res, '密码名称不能为空')
return
}
if(params.password !== params.password2){
sendError(res, '两次密码必须相同')
return
}
let username = params.username
let password = params.password
let ip = utils.getClientIp(req)
let date = moment().format('YYYY-MM-DD HH:mm:ss');
let csql = eval('`'+sql.USER_REGISTER+'`');
console.log('[SQL:]', csql);
query(csql, (err, result, fields) => {
if (err) {
console.log('[SELECT ERROR]:', err.message)
return sendError(res, err.message)
}
res.send(result)
})
})
// apiRoutes的路由接口都还校验token
var apiRoutes = express.Router();
apiRoutes.use(function(req, res, next) {
var token = req.body.token || req.query.token || req.headers['x-token'];
if (token) {
// 解码 token (验证 secret 和检查有效期(exp))
jwt.verify(token, app.get('superSecret'), function(err, decoded) {
if (err) {
sendError(res, 'token已过期', 1000)
} else {
// 如果验证通过,在req中写入解密结果
req.decoded = decoded;
next(); //继续下一步路由
}
});
} else {
// 没有拿到token 返回错误
sendError(res, '请传入token')
}
})
// 获取所有任务
apiRoutes.get('/task/get-task-list', (req, res) => {
...
})
...
app.use('/', apiRoutes);
总结
这里用户密码也没有加密操作,直接是明文,登录查询,注册插入也做的比较简单,都没有做过多的校验,主要是示例,希望能给大家带来启发。详细代码后面优化,补充吧。
代码请戳:Todo List GitHub代码库