前言

Todo List项目到这里已经到了非常重要的部分,涉及到用户部分。权限,多人协同操作等,都是非常重要,且复杂的地方,所以这里分为几个章节,一步一步的完成。

本章节主要讲讲用户登录,注册会话部分。

代码请戳:Todo List GitHub代码库

Todo List: 多人协同处理待办事项,权限管理 - 第七章(第1节)

本次《todo list: Vue待办事项任务管理》,分为一下章节,有兴趣的同学可以持续关注。

第一章: 初识(项目搭建、基本功能组件实现)

第二章: 数据动态化处理(localStorage + Vuex),可新增,可编辑

第三章:待办事项自定义分组

第四章:待办事项添加描述图片等信息

第五章:Node + Express 搭建服务端连接Mysql

第六章:Client端与Server端交互,待办任务入库等

第七章:多人协同处理待办事项,权限管理

第八章:完结:线上发布

初步定义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 } }) }) }

Todo List: 多人协同处理待办事项,权限管理 - 第七章(第1节)

Todo List: 多人协同处理待办事项,权限管理 - 第七章(第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代码库