[BE201] 後端中階:簡單會員註冊系統


Posted by s103071049 on 2021-08-09

做一個簡單會員註冊系統

目標功能:登入、登出、註冊

  1. 新增 table 表 users - id / username (設定 unique) / password (會經過 hash 進去,開大一點) / nickname / create_at
  2. 建立 user 的 model => user.js 在 model 資料夾內 => index.js 引入 const userController = require('./controllers/user')
  3. 建立 user 的 controller => user.js 在 controllers 資料夾內
  4. 調整畫面 => 增加首頁
  5. 實做註冊頁面
  6. 調整 session 機制
  7. 處理明碼問題 => 安裝 bcrypt (node.bcrypt.js),可以利用這個參數讓密碼變得複雜 const saltRounds = 10;
  8. login 功能
  9. 除蟲 => 1. 輸入錯的帳號密碼無錯誤顯示
// step 2:user model
// model 用非同步的方式以 cb 拿資料
const db = require('../db')
const userModel = {
  add: (user, cb) => { // 傳入 user object 好做擴充
    db.query('insert into users (username, password, nickname) values (?, ?, ?)',
      [user.username, user.password, user.nickname],
      (err, result) => {
        if (err) return cb(err)
          return cb(null, result)
      }
    )
  },
  get: (username, cb) => {
    db.query('select * from users where username = ?', [username],
      (err, results) => {
        if (err) return cb(err)
          return  cb(null, results[0])
      }
    )
  },
}
module.exports = userModel

處理 login、logout 等功能

// index.js
app.get('/login', userController.login)
app.post('/login', userController.handleLogin)

// user.js in Controllers

const userModel = require('../models/user')
const userController = {
  login: (req, res) => {// 渲染登入畫面的 method
    res.render('login')
  },
  handleLogin: (req, res) => {
    if (req.body.password === 'abc') {
      req.session.isLogin = true
      res.redirect('/')
    } else {
      req.flash('errorMessage', 'Please input the correct password')
      res.redirect('/login')
    }
  },
  logout: (req, res) => {
    req.session.isLogin = false
    res.redirect('/todos')
  }
}

module.exports = userController
// step4 調整畫面

// index.js
app.get('/', (req, res) => {
  res.render('index') // 首頁
})

//index.ejs
<h1>簡易會員系統</h1>
<% if (isLogin) { %>
  <a href="/logout">登出</a>
<% } else { %>
  <a href="/register">註冊</a>
  <a href="/login">登入</a>
<% } %>

處理註冊功能

// step 5:實做註冊功能

// inde.js
app.get('/register', todoController.register)
app.post('/register', userController.handleRegister)

// userController 裡面加上
  register: (req, res) => {
    res.render('/register') // 只要渲染頁面就好
  },
  handleRegister: (req, res) => {

  }

// view
// register.ejs
<h1>註冊頁面</h1>
<form action="/register" method="POST">
  <div>username: <input type="text" name="username"></div>
  <div>nickname: <input type="text" name="nickname"></div>
  <div>password: <input type="password" name="password"></div>
  <input type="submit">
</form>

處理 handleRegister 的部份

// register.ejs
<h2><%= errorMessage %></h2>

// user.js in 資料夾 controllers 
  handleRegister: (req, res) => {
    const {username, password, nickname} = req.body
    if (!username || !password || !nickname) {
      return req.flash('errorMessage', 'you should fill in all input')
    }
    userModel.add({
      username,
      nickname,
      password // 把明碼寫入
    }, (err) => {
      if (err) {
        return req.flash('errorMessage', err.toString())
      }
      req.session.username = username // 讓他可以直接登入
      res.redirect('/')
    })
  }

req.session.username 是否為空判斷登入狀況

// index.js
app.use((req, res, next) => {
  res.locals.username = req.session.username
  res.locals.errorMessage = req.flash('errorMessage')
  next()
})

// index.ejs
<h1>簡易會員系統</h1>
<% if (username) { %>
  <a href="/logout">登出</a>
<% } else { %>
  <a href="/register">註冊</a>
  <a href="/login">登入</a>
<% } %>

// todo.js in controllers
logout: (req, res) => {
    req.session.username = false
    res.redirect('/todos')
  }

處理明碼問題,請 bcrypt 這個 library 將我的 password 做 hash,產生的 hash 再當作密碼寫入

// stpe 7
// todo.js in controllers 
const bcrypt = require('bcrypt')
const saltRounds = 10

// handle register 的部份
  handleRegister: (req, res) => {
    const {username, password, nickname} = req.body
    if (!username || !password || !nickname) {
      return req.flash('errorMessage', 'you should fill in all input')
    }
    bcrypt.hash(password, saltRounds, (err, hash) => {
      if (err) {
        return req.flash('errorMessage', err.toString())
      }
      userModel.add({
        username,
        nickname,
        password: hash
      }, (err) => {
        if (err) return req.flash('errorMessage', err.toString())
        req.session.username = username
        res.redirect('/')
      })
    })
  }

登入功能

//step 8
// login.ejs in views
<h1>登入頁面</h1>
<h2><%= errorMessage %></h2>
<form action="/login" method="POST">
  <div>username: <input type="text" name="username"></div>
  <div>password: <input type="password" name="password"></div>
  <input type="submit">
</form>

// user.js in controllers
  handleLogin: (req, res) => {
    // 檢查有無輸入帳密
    const {username, password} = req.body
    if (!username || !password) {
      return req.flash('errorMessage', '未輸入齊全')
    }
    // 資料齊全就從資料庫拿出資料
    userModel.get(username, (err, user) => {
      if (err) {
        return req.flash('errorMessage', err.toString())
      }
      bcrypt.compare(password, user.password, (err, isSuccess) => {
        if (err || !isSuccess) {
          return req.flash('errorMessage', '密碼錯誤')
        }
        req.session.username = user.username // 登入成功
        res.redirect('/')
      })
    })
  }

除蟲

  1. TypeError: Cannot read property 'password' of undefined => user 找不到不是 err 原本的寫法 if (err) {
  2. 卡住,因為只有設定 flash messenger return req.flash('errorMessage', err.toString()),並沒有說要如何處理 response,所以就會卡住在那邊
    userModel.get(username, (err, user) => {
      if (err) {
        req.flash('errorMessage', err.toString())
        return res.redirect('back') // flash 完導回上一頁
      }
      if (!user) { // 多考慮 !user 的狀況
        return req.flash('errorMessage', '用戶不存在')
        return res.redirect('back')
      }

每次錯誤訊息處理完後都導回,每個地方都要加這個 return res.redirect('back') 很煩,所以可以這樣做

// todo.js in controllers
const userController = {
  get: (req, res) => {
  },
  login: (req, res) => {// 渲染登入畫面的 method
    res.render('login')
  },
  handleLogin: (req, res, next) => {
    // 檢查有無輸入帳密
    const {username, password} = req.body
    if (!username || !password) {
      req.flash('errorMessage', '未輸入齊全')
      return next()
    }
    // 資料齊全就從資料庫拿出資料
    userModel.get(username, (err, user) => {
      if (err) {
        req.flash('errorMessage', err.toString())
        return next()
      }
      if (!user) {
        req.flash('errorMessage', '用戶不存在')
        return next()
      }
      bcrypt.compare(password, user.password, (err, isSuccess) => {
        if (err || !isSuccess) {
          req.flash('errorMessage', '密碼錯誤')
          return next()
        }
        req.session.username = user.username // 登入成功
        res.redirect('/')
      })
    })
  },
  logout: (req, res) => {
    req.session.username = false
    res.redirect('/')
  },
  register: (req, res) => {
    res.render('user/register') // 只要渲染頁面就好
  },
  handleRegister: (req, res, next) => {
    const {username, password, nickname} = req.body
    if (!username || !password || !nickname) {
      req.flash('errorMessage', 'you should fill in all input')
      return next()
    }
    bcrypt.hash(password, saltRounds, (err, hash) => {
      if (err) {
        req.flash('errorMessage', err.toString())
        return next()
      }
      userModel.add({
        username,
        nickname,
        password: hash
      }, (err) => {
        if (err) {
          req.flash('errorMessage', err.toString())
          return next()
        }
        req.session.username = username
        res.redirect('/')
      })
    })
  }
}

module.exports = userController
// index.js
function redirectBack(req, res, next) {
  res.redirect('back')
}
app.post('/register', userController.handleRegister, redirectBack)
app.post('/login', userController.handleLogin, redirectBack)

小結

  1. 如何以剛學過的知識,寫出簡易會員系統
  2. index.js 從路由出發
  3. controller 裡面呼叫相對應的 model,若有錯誤以 flash middle ware 處理錯誤訊息,處理完後導回要去的頁面、利用 bcrypt 比對密碼是否正確
  4. model 與資料庫串資料

template engine 會幫我們處理 xss
等號會自動做 escape,減號就會有甚麼輸出甚麼

<%= <h1>hello</h1> %> 
<%- <h1>hello</h1> %>

code

// index.js
app.use((req, res, next) => {
  res.locals.username = req.session.username
  res.locals.errorMessage = req.flash('errorMessage')
  next()
})

function redirectBack(req, res, next) {
  res.redirect('back')
}

app.get('/', (req, res) => {
  res.render('index')
})

app.get('/login', userController.login)
app.post('/login', userController.handleLogin, redirectBack)

app.get('/register', userController.register)
app.post('/register', userController.handleRegister, redirectBack)

// Controller
const bcrypt = require('bcrypt')
const saltRounds = 10
const userModel = require('../models/user')
const userController = {
  login: (res, req) => {
    res.render('login')
  },
  handleLogin: (res, req, next) => {
    const {username, password} = req.body
    if (!username || !password) {
      req.flash('errorMessage', '資料輸入不齊全')
      return next()
    }
    userModel.get(username, (err, user) => {
      if (err) {
        req.flash('errorMessage', err.toString())
        return next()
      }
      if (!user) {
        req.flash('errorMessage', '用戶不存在')
        return next()
      }
      bcrypt.compare(password, user.password, (err, isSuccess) => {
        if (err || !isSuccess) {
          req.flash('errorMessage'. '密碼不正確')
          return res.redirect('back')
        }
        req.session.username = user.username
        res.redirect('/')
      })
    })
  },
  logout: (req, res) => {
    req.session.username = false
    res.redirect('/')
  },
  register: (req, res) => {
    res.render('/register')
  },
  handleRegister: (req, res, next) => {
    const {username, password, nickname} = req.body
    if (!username || !password || !nickname) {
      req.flash('errorMessage', '資料輸入不齊全')
      return next()
    }
    bcrypt.hash(password, saltRounds, (err, hash) => {
      if (err) {
        req.flash('errorMessage', err.toString())
        return next()
      }
      userModel.add({
        username,
        nickname,
        password: hash
      }, (err) => {
        if (err) {
          req.flash('errorMessage', '資料庫處理異常')
          return next()
        }
        req.session.username = username
        res.redirect('/')
      })
    })
  }
}
module.exports = userController

// Model
const db = require('../db')
const userModel = {
  add: (user, cb) => {
    db.query(
      'insert into users (username), password, nickname values(?, ?, ?)',
      [user.username, user.password, user.nickname],
      (err, result) => {
        if (err) return cb(err)
        return cb(null, result)
      }
    )
  },
  get: (username, cb) {
    db.query(
      'select * from users where username = ?', 
      [username],
      (err, result) => {
        if (err) return cb(err)
        return cb(null, result[0])
      }
    )
  }
}

module.exports = userModel

// views
1. index.ejs
/*
<h1>簡易會員系統</h1>
<% if (username) { %>
  <a href= "/logout">登出</a>
<% } else { %>
  <a href= "/register">註冊</a>
  <a href= "/login">登出</a>
*/

2. register.ejs
/*
<h1>註冊頁面</h1>
<h2><%= errorMessage %></h2>
<form action= "/register" method="POST">
  <div>username: <input type="text" name="username"></div>
  <div>nickname: <input type="text" name="nickname"></div>
  <div>password: <input type="text" name="password"></div>
  <input type="submit">
</form>
*/
3. login.ejs
/*
<h1>登入頁面</h1>
<h2><%= errorMessage %></h2>
<form action= "/login" method="POST">
  <div>username: <input type="text" name="username"></div>
  <div>password: <input type="text" name="password"></div>
  <input type="submit">
</form>
*/









Related Posts

Web開發學習筆記10 — 預設參數、Spread Operator、Rest Operator、解構賦值、陣列方法的練習筆記

Web開發學習筆記10 — 預設參數、Spread Operator、Rest Operator、解構賦值、陣列方法的練習筆記

反射方法操作

反射方法操作

The way open PDF in android.

The way open PDF in android.


Comments