做一個簡單會員註冊系統
目標功能:登入、登出、註冊
- 新增 table 表 users - id / username (設定 unique) / password (會經過 hash 進去,開大一點) / nickname / create_at
- 建立 user 的 model => user.js 在 model 資料夾內 => index.js 引入
const userController = require('./controllers/user')
- 建立 user 的 controller => user.js 在 controllers 資料夾內
- 調整畫面 => 增加首頁
- 實做註冊頁面
- 調整 session 機制
- 處理明碼問題 => 安裝 bcrypt (node.bcrypt.js),可以利用這個參數讓密碼變得複雜
const saltRounds = 10;
- login 功能
- 除蟲 => 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('/')
})
})
}
除蟲
- TypeError: Cannot read property 'password' of undefined => user 找不到不是 err 原本的寫法
if (err) {
- 卡住,因為只有設定 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)
小結
- 如何以剛學過的知識,寫出簡易會員系統
- index.js 從路由出發
- controller 裡面呼叫相對應的 model,若有錯誤以 flash middle ware 處理錯誤訊息,處理完後導回要去的頁面、利用 bcrypt 比對密碼是否正確
- 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>
*/