• Nodejs学习笔记(十五)—Node.js + Koa2 构建网站简单示例


    前言

      前面一有写到一篇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)

    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/

     源码详见http://bijian1013.iteye.com/blog/2425085,虽然是示例结构,但是尽量按照平常做项目的想法去实现的,有兴趣的动手去搭项目做才会理解一些思路,代码都放在文章中了。

     

    此系列的源代码可到http://bijian1013.iteye.com/blog/2425085下载。 

    文章来源:https://www.cnblogs.com/zhongweiv/p/nodejs_koa2_webapp.html

  • 相关阅读:
    9.19题解
    9.18题解
    改码风
    找到了几个好的网站
    题目链接
    二分上机训练题解
    二分例题简单说明
    贪心上机训练题解
    贪心算法例题简单说明
    Johnson法则证明
  • 原文地址:https://www.cnblogs.com/flyingeagle/p/9193191.html
Copyright © 2020-2023  润新知