目錄
前言
前面一有寫到一篇Node.js+Express構建網站簡單示例:http://www.cnblogs.com/zhongweiv/p/nodejs_express_webapp.html
這篇還是用以前的例子, 用Node.js+Koa2構建
Koa: https://github.com/koajs/koa
Koa就不多介紹了,前面也寫過Express,同一個團隊打造,前面也過express文章,對比着看,自然可以看出些優點!
搭建項目及其它准備工作
CREATE DATABASE IF NOT EXISTS nodesample CHARACTER SET UTF8; USE nodesample; SET FOREIGN_KEY_CHECKS=0; DROP TABLE IF EXISTS `userinfo`; CREATE TABLE `userinfo` ( `Id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵', `UserName` varchar(64) NOT NULL COMMENT '用戶名', `UserPass` varchar(64) NOT NULL COMMENT '用戶密碼', PRIMARY KEY (`Id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用戶信息表';
安裝koa-generator: https://github.com/17koa/koa-generator
npm install -g koa-generator
安裝成功后下圖(版本:1.1.16)

然后創建Koa2項目,安裝相關依賴項
cd 工作目錄
koa2 項目名
cd 項目目錄 && npm install
1.安裝使用MySQL需要的包
npm install --save mysql
沒有使用過的可以看我以前寫的相關操作文章:http://www.cnblogs.com/zhongweiv/p/nodejs_mysql.html
2.安裝ejs(koa2默認為jade,我習慣使用ejs)
npm install --save ejs
沒有使用過的可以看我以前寫的相關操作文章:http://www.cnblogs.com/zhongweiv/p/nodejs_express.html
3.安裝Session存儲相關包(存儲到redis)
npm install koa-session https://github.com/koajs/session
npm install --save koa-session
koa-session-redis https://github.com/Chilledheart/koa-session-redis
npm install --save koa-session-redis
1.刪除掉創建項目后自帶的views和routes下的文件
2.重新規划項目目錄,規划后如下

目錄規則解釋:
1.新增pub目錄:主要為了統一存放"數據訪問"、"業務邏輯"、"公共方法文件"、"數據庫幫助文件"、"配置文件"等
2.新增pub目錄下utils目錄:主要為了統一存放類似"公共函數文件"、"返回值文件"、"枚舉文件"等公共文件
3.新增pub目錄下config目錄:主要為了統一存放各種類型的配置文件
4.新增pub目錄下db目錄:主要為了統一存放各種數據庫幫助類,比如:"mysql-helper.js"、"mongo-helper.js"等等
5.新增pub目錄下model目錄:主要為了統一存放各種數據庫各表CURD操作
6.新增pub目錄下bll目錄:主要為了統一存放各種業務邏輯的具體實現
配置文件
從上面的圖可以看出,我在pub下新建的config目錄下新建了一個config.js
這個config.js中將編寫“開發環境”和“發布環境”中所需的配置,代碼如下
/** * 配置文件 */ //發布配置 const production = { //服務器端口 SERVER_PORT : 3000, //REDIS配置 REDIS: { host: 'localhost', port: 6379, password: "abcd", maxAge: 3600000 }, //MYSQL數據庫配置 MYSQL: { host: "localhost", user: "root", password: "abcd", port: "3306", database: "nodesample", supportBigNumbers: true, multipleStatements: true, timezone: 'utc' } } //開發配置 const development = { //服務器端口 SERVER_PORT : 3000, //REDIS配置 REDIS: { host: 'localhost', port: 6379, password: "abcd", maxAge: 3600000 }, //MYSQL數據庫配置 MYSQL: { host: "localhost", user: "root", password: "abcd", port: "3306", database: "nodesample", supportBigNumbers: true, multipleStatements: true, timezone: 'utc' } } const config = development module.exports = config
規划示例路由,並新建相關文件
示例中將有注冊、登錄功能,先規划好路由,新建routes、views下的相關需要的文件(如項目目錄圖中文件),並修改app.js文件
const Koa = require('koa')
const app = new Koa()
const views = require('koa-views')
const json = require('koa-json')
const onerror = require('koa-onerror')
const bodyparser = require('koa-bodyparser')
const logger = require('koa-logger')
const config = require('./pub/config/config.js');
const session = require('koa-session');
const RedisStore = require('koa2-session-redis');
const index = require('./routes/index')
const reg = require('./routes/reg')
const login = require('./routes/login')
const logout = require('./routes/logout')
// error handler
onerror(app)
// middlewares
app.use(bodyparser({
enableTypes:['json', 'form', 'text']
}))
app.use(json())
app.use(logger())
app.use(require('koa-static')(__dirname + '/public'))
app.use(views(__dirname + '/views', {
extension: 'ejs'
}))
// logger
app.use(async (ctx, next) => {
const start = new Date()
await next()
const ms = new Date() - start
console.log(`${ctx.method} ${ctx.url} - ${ms}ms`)
})
app.keys = ['Porschev'];
const redis_conf = {
key: 'Porschev',
maxAge: config.REDIS.maxAge,
overwrite: true,
httpOnly: true,
rolling: false,
sign: true,
store: new RedisStore({
host: config.REDIS.host,
port: config.REDIS.port,
password: config.REDIS.password
})
};
app.use(session(redis_conf, app));
// routes
app.use(index.routes(), index.allowedMethods())
app.use(reg.routes(), reg.allowedMethods())
app.use(login.routes(), login.allowedMethods())
app.use(logout.routes(), logout.allowedMethods())
// error-handling
app.on('error', (err, ctx) => {
console.error('server error', err, ctx)
});
app.listen(config.SERVER_PORT, () => {
console.log(`Starting at port ${config.SERVER_PORT}!`) });
module.exports = app
注意看紅色標記修改或增加的部分
實現數據訪問和業務邏輯相關方法
1.首先編寫一個mysql-helper.js方便以連接池的方式進行操作
const config = require('./../config/config.js')
const mysql = require("mysql")
const pool = mysql.createPool(config.MYSQL)
let query = function(sql, args) {
return new Promise((resolve, reject) => {
pool.getConnection(function(err, connection) {
if (err) {
resolve(err)
} else {
connection.query(sql, args, (err, result) => {
if (err) {
reject(err)
} else {
resolve(result)
}
connection.release()
})
}
})
})
}
module.exports = {
query
}
2.編寫數據訪問相關方法(model目錄下的userinfo.js),如下
const mysqlHelper = require('./../db/mysql-helper.js')
const userinfo = {
/**
* 增加一條數據
* @param {object} args 參數
* @return {object} 結果
*/
async add ( args ) {
let sql = 'INSERT INTO userinfo(UserName, UserPass) VALUES(?, ?)'
let params = [args.username, args.userpass]
let result = await mysqlHelper.query(sql, params)
return result
},
/**
* 根據UserName得到一條數據
* @param {object} args 參數
* @return {object} 結果
*/
async getByUserName( args ){
let sql = 'SELECT Id, UserName, UserPass FROM userinfo WHERE UserName = ?'
let params = [args.username]
let result = await mysqlHelper.query(sql, params)
return result
},
/**
* 根據UserName得到數量
* @param {object} args 參數
* @return {object} 結果
*/
async getCountByUserName( args ){
let sql = 'SELECT COUNT(1) AS UserNum FROM userinfo WHERE UserName = ?'
let params = [args.username]
let result = await mysqlHelper.query(sql, params)
return result
},
}
module.exports = userinfo
3.在寫業務邏輯之前先規划好返回值(utils目錄下retcode.js)
/* * 返回碼 */ const RetCode = { SessionExpired: -1, //session過期 Fail: 0, //失敗 Success: 1, //成功 ArgsError: 2, //參數錯誤 UserExisted: 10, //用戶已經存在 UsernameOrPasswordError: 11, //用戶名或者密碼錯誤 UserNotExist: 12, //用戶不存在 }; module.exports = RetCode
4.編寫“登錄”、“注冊”等業務邏輯(bll下userinfo.js)
const usermodel = require('./../model/userinfo.js')
const retCode = require('./../utils/retcode.js')
const userinfo = {
/**
* 注冊
* @param {object} ctx 上下文
* @return {object} 結果
*/
async register ( ctx ) {
let form = ctx.request.body
const args = {
username: form.username,
userpass: form.userpass
}
let result = {
code: retCode.Success,
data: null
}
//驗證非空
if(!args.username || !args.userpass){
result.code = retCode.ArgsError
return result
}
//根據用戶名得到用戶數量
let userNumResult = await usermodel.getCountByUserName(args)
//用戶名已被注冊
if(userNumResult[0].UserNum > 0){
result.code = retCode.UserExisted
return result
}
//插入注冊數據
let userResult = await usermodel.add(args)
if(userResult.insertId <= 0){
result.code = retCode.Fail
return result
}
return result
},
/**
* 登錄
* @param {object} ctx 上下文
* @return {object} 結果
*/
async login ( ctx ) {
let form = ctx.request.body
const args = {
username: form.username,
userpass: form.userpass
}
let result = {
code: retCode.Success,
data: null
}
//驗證非空
if(!args.username || !args.userpass){
result.code = retCode.ArgsError
return result
}
//根據用戶名得到用戶信息
let userResult = await usermodel.getByUserName(args)
//用戶不存在
if(userResult.length == 0){
result.code = retCode.UserNotExist
return result
}
//用戶名或密碼錯誤
if(userResult[0].UserName != args.username || userResult[0].UserPass != args.userpass){
result.code = retCode.UsernameOrPasswordError
return result
}
//將用戶ID存入Session中
ctx.session = {id: userResult[0].Id}
return result
},
}
module.exports = userinfo
注冊
1.views目錄下reg.ejs
<html> <head> <title>Nodejs學習筆記(十五)--- Node.js + Koa2 構建網站簡單示例</title> </head> <body> <h1><%= title %></h1> 登錄名:<input type="text" id="txtUserName" maxlength="20" /> <br/> <br/> 密碼:<input type="password" id="txtUserPwd" maxlength="12" /> <br/> <br/> 密碼:<input type="password" id="txtUserRePwd" maxlength="12" /> <br/> <br/> <input type="button" id="btnSub" value="注冊" /> </body> </html> <script src="/javascripts/jquery-1.11.2.min.js" type="text/javascript"></script> <script src="/javascripts/md5.js" type="text/javascript"></script> <script type="text/javascript"> $(function(){ $('#btnSub').on('click', function(){ var $txtUserName = $('#txtUserName'), txtUserNameVal = $.trim($txtUserName.val()), $txtUserPwd = $('#txtUserPwd'), txtUserPwdVal = $.trim($txtUserPwd.val()), $txtUserRePwd = $('#txtUserRePwd'), txtUserRePwdVal = $.trim($txtUserRePwd.val()); if(txtUserNameVal.length == 0){ alert('用戶名不能為空'); return false; } if(txtUserPwdVal.length == 0){ alert('密碼不能為空'); return false; } if(txtUserRePwdVal.length == 0){ alert('重復密碼不能為空'); return false; } if(txtUserPwdVal != txtUserRePwdVal){ alert('兩次密碼不一致'); return false; } $.ajax({ url: '/reg', type: 'POST', dataType: 'json', data: { username: txtUserNameVal, userpass: hex_md5(txtUserPwdVal) }, beforeSend: function (xhr) {}, success: function (res) { if (res != null && res.code) { var retVal = parseInt(res.code); switch (retVal) { case 2: alert('輸入有誤'); break; case 0: alert('注冊失敗'); break; case 1: alert('注冊成功!'); location.href = '/login' break; case 10: alert('用戶已注冊'); break; } } else { alert('操作失敗'); } }, complete: function (XMLHttpRequest, textStatus) {}, error: function (XMLHttpRequest, textStatus, errorThrown) { alert('操作失敗'); } }); }) }); </script>
2.routes目錄下reg.js
const router = require('koa-router')()
const userBll = require('./../pub/bll/userinfo.js')
const title = '注冊'
router.prefix('/reg')
router.get('/', async (ctx, next) => {
await ctx.render('reg', { title })
})
router.post('/', async (ctx, next) => {
let result = await userBll.register(ctx)
ctx.body = result;
})
module.exports = router
登錄
1.views目錄下login.ejs
<html> <head> <title>Nodejs學習筆記(十五)--- Node.js + Koa2 構建網站簡單示例</title> </head> <body> <h1><%= title %></h1> 登錄名:<input type="text" id="txtUserName" maxlength="20" /> <br/> <br/> 密碼:<input type="password" id="txtUserPwd" maxlength="12" /> <br/> <br/> <input type="button" id="btnSub" value="登錄" /> </body> </html> <script src="/javascripts/jquery-1.11.2.min.js" type="text/javascript"></script> <script src="/javascripts/md5.js" type="text/javascript"></script> <script type="text/javascript"> $(function(){ $('#btnSub').on('click', function(){ var $txtUserName = $('#txtUserName'), txtUserNameVal = $.trim($txtUserName.val()), $txtUserPwd = $('#txtUserPwd'), txtUserPwdVal = $.trim($txtUserPwd.val()); if(txtUserNameVal.length == 0){ alert('用戶名不能為空'); return false; } if(txtUserPwdVal.length == 0){ alert('密碼不能為空'); return false; } $.ajax({ url: '/login', type: 'POST', dataType: 'json', data: { username: txtUserNameVal, userpass: hex_md5(txtUserPwdVal) }, beforeSend: function (xhr) {}, success: function (res) { if (res != null && res.code) { var retVal = parseInt(res.code); switch (retVal) { case 2: alert('輸入有誤'); break; case 0: alert('登錄失敗'); break; case 1: alert('登錄成功!'); location.href = '/' break; case 11: alert('用戶名或者密碼錯誤'); break; case 12: alert('用戶不存在'); break; } } else { alert('操作失敗'); } }, complete: function (XMLHttpRequest, textStatus) {}, error: function (XMLHttpRequest, textStatus, errorThrown) { alert('操作失敗'); } }); }) }); </script>
2.routes目錄下login.js
const router = require('koa-router')()
const userBll = require('./../pub/bll/userinfo.js')
const title = '登錄'
router.prefix('/login')
router.get('/', async (ctx, next) => {
await ctx.render('login', { title })
})
router.post('/', async (ctx, next) => {
let result = await userBll.login(ctx);
ctx.body = result;
})
module.exports = router
首頁
1.views目錄下index.ejs
<html> <head> <title>Nodejs學習筆記(十五)--- Node.js + Koa2 構建網站簡單示例</title> </head> <body> <h1><%= title %></h1> <% if(id != null) {%> <h3>登錄用戶ID:<%= id %> <a id="btnLogOut" href="javascript:void(0);">安全退出</a></h3> <% } %> </body> </html> <script src="/javascripts/jquery-1.11.2.min.js" type="text/javascript"></script> <script type="text/javascript"> $(function(){ $('#btnLogOut').on('click', function(){ if(!confirm('確認要退出嗎?')){ return; } $.ajax({ url: '/logout', type: 'POST', dataType: 'json', data: {}, beforeSend: function (xhr) {}, success: function (res) { if (res != null && res.code) { var retVal = parseInt(res.code); switch (retVal) { case 0: alert('失敗'); break; case 1: alert('成功!'); location.href = '/login' break; } } else { alert('操作失敗'); } }, complete: function (XMLHttpRequest, textStatus) {}, error: function (XMLHttpRequest, textStatus, errorThrown) { alert('操作失敗'); } }); }) }); </script>
2.routes目錄下index.js
const router = require('koa-router')()
const title = '首頁'
router.get('/', async (ctx, next) => {
//判斷登錄
if(!ctx.session || !ctx.session.id){
await ctx.redirect('/login')
}else{
const id = ctx.session.id;
await ctx.render('index', { title, id })
}
})
module.exports = router
index.js文件中實現如果不存在session則跳回登錄頁
安全退出
1.routes目錄下logout.js
const router = require('koa-router')()
const retCode = require('./../pub/utils/retcode.js')
router.prefix('/logout')
router.get('/', async (ctx, next) => {
await ctx.render('logout', {})
})
router.post('/', async (ctx, next) => {
ctx.session = null;
let result = {
code: retCode.Success,
data: null
}
ctx.body = result;
})
module.exports = router
寫在之后
沒有去說一些細節API,寫這篇主要可以對比 Nodejs學習筆記(七)--- Node.js + Express 構建網站簡單示例 來看,完全是一親的示例,只是這次用的Koa2,方便大家看看Koa2和express寫出來的不同
總的來說Koa2還是比較好上手,async、await這個對於有C#語言基礎的來說也比較親切,不用二次理解
可以對比一下express時的各種嵌套回調寫法,Koa2寫好更優雅、更易閱讀
示例有限,其它操作通過官網查找API或github找一些組件來動手試,比如最常用的一些功能:操作cookies、上傳文件、session存儲到其它介質等
參考資料: https://koa.bootcss.com/
老規矩不放源碼,雖然是示例結構,但是盡量按照平常做項目的想法去實現的,有興趣的動手去搭項目做才會理解一些思路,代碼都放在文章中了,有問題留言^_^!
