目录
- 前言
- 搭建项目及其它准备工作
- 创建数据库
- 创建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-sessionhttps://github.com/koajs/session
npm install --save koa-session
koa-session-redishttps://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 = developmentmodule.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 handleronerror(app)// middlewaresapp.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‘}))// loggerapp.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));// routesapp.use(index.routes(), index.allowedMethods())app.use(reg.routes(), reg.allowedMethods())app.use(login.routes(), login.allowedMethods())app.use(logout.routes(), logout.allowedMethods())// error-handlingapp.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" maxlength="20" /><br/><br/>密码:<input type="password" maxlength="12" /><br/><br/>密码:<input type="password" maxlength="12" /><br/><br/><input type="button" 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" maxlength="20" /><br/><br/>密码:<input type="password" maxlength="12" /><br/><br/><input type="button" 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 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/
老规划不放源码,虽然是示例结构,但是尽量按照平常做项目的想法去实现的,有兴趣的动手去搭项目做才会理解一些思路,代码都放在文章中了,有问题留言^_^!
Nodejs学习笔记(十五)--- Node.js + Koa2 构建网站简单示例
原文地址:http://www.cnblogs.com/zhongweiv/p/nodejs_koa2_webapp.html