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

614 阅读5分钟

前言

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

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

代码请戳:Todo List GitHub代码库

本次《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
      }
    })
  })
}

20190620104447.jpg

20190620104503.jpg

/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代码库