Nodejs學習筆記(十五)--- Node.js + Koa2 構建網站簡單示例

来源:http://www.cnblogs.com/zhongweiv/archive/2017/11/16/nodejs_koa2_webapp.html
-Advertisement-
Play Games

目錄 前言 搭建項目及其它準備工作 創建資料庫 創建Koa2項目 安裝項目其它需要包 清除冗餘文件並重新規劃項目目錄 配置文件 規劃示例路由,並新建相關文件 實現數據訪問和業務邏輯相關方法 編寫mysql-helper.js 編寫數據訪問方法 規劃業務邏輯返回值 編寫業務邏輯 註冊 登錄 首頁 安全 ...


目錄

前言

  前面一有寫到一篇Node.js+Express構建網站簡單示例:http://www.cnblogs.com/zhongweiv/p/nodejs_express_webapp.html

  這篇還是用以前的例子, 用Node.js+Koa2構建

 

  Koa:   https://github.com/koajs/koa

       http://koa.bootcss.com  (中文)

 

  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='用戶信息表';

創建Koa2項目

  安裝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
retcode.js

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>
reg.ejs

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: 	   

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 詳情參見:https://www.cnblogs.com/landiljy/p/5764515.html 1.@PostConstruct說明 被@PostConstruct修飾的方法會在伺服器載入Servlet的時候運行,並且只會被伺服器調用一次,類似於Serclet的inti()方法。被@Pos... ...
  • 1.功能: 只能有一個實例的類,用於類似計數、記憶體池的情況。 2.實現方法: [1]構造函數設置為private,因此不能在外部創建實例。 [2]提供一個public方法訪問實例。 [3]析構函數,析構函數是為了銷毀這個類的成員變數,private和public都可以,但是析構函數裡面不能delet ...
  • 歡迎想學習網頁設計的伙伴們,我會定期開始錄製免費的網頁設計教程,主要是作為一種學習的分享。 首先,給大家介紹一下,我在紐特邏輯工作,主要從事前端設計,本次課程循序漸進,難度初級,最後是一個設計出題網頁為結束。 開始之前,要做一些準備: 1.軟體準備 (1)<首先安裝>Java開發環境JDK,一種用於 ...
  • 函數式編程中,一切皆為函數,這個函數一般不是類級別的,其可以保存在變數中,可以當做參數或返回值,是函數級別的抽象和重用,將函數作為可重用的基本模塊,就像面向對象中一切皆為對象,把所有事物抽象為類,面向對象編程通過繼承和組合來實現類或模塊重用,而函數式編程通過局部套用來實現函數重用;兩種編程模式相輔相 ...
  • 回到目錄 Dapper作為小型ORM的代表作品被我們應用到了dotnet core的項目中,下麵將把自己在項目中使用dapper進行curd操作的過程寫一下,後期可能會遇到一些問題,大叔也會在這個系列之中進行完善,希望對各位學生有所幫助! 一 安裝nuget的dapper包包 二 在startup中 ...
  • 問題 當目錄下的文件數量較大時,用webstorm打開會出現卡頓,甚至卡死現象,例如:node_modules目錄 解決方案 不讓webstorm索引該目錄下的文件步驟:1.node_modules目錄右鍵,彈出菜單2.選擇Mark Directory as3.再選擇exclude這樣操作後,nod ...
  • mintui是餓了麽團隊針對vue開發的移動端組件庫,方便實現移動端的一些功能,這裡只用了Loadmore功能實現移動端的上拉分頁刷新,下拉載入數據.mintui官網:http://mint-ui.github.io/#!/zh-cn PS:有個坑一定要註意就是註釋里說的iPhone里loadmor ...
  • # new Vue({ vue所有的數據都是放到data裡面的 # data:{ vue對象的數據 # a:1,對象 # b:[] , # } # methods:{vue對象的方法 # dosomthing: function() # { # this.a ++ # console.log(thi... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...