diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..2f9e3a2
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+node_modules/
+npm-debug.log
+_book/
+demo/*/node_modules
+demo/*/npm-debug.log
+demo/*/upload-files/
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..f116c06
--- /dev/null
+++ b/README.md
@@ -0,0 +1,2 @@
+# hello world
+
diff --git a/SUMMARY.md b/SUMMARY.md
new file mode 100644
index 0000000..b81945b
--- /dev/null
+++ b/SUMMARY.md
@@ -0,0 +1,41 @@
+# Summary
+
+* [koa2开始]()
+ * [快速开始](note/start/quick.md)
+ * [async/await使用](note/start/async.md)
+ * [koa2简析结构](note/start/info.md)
+ * [koa中间件开发与使用](note/start/middleware.md)
+* [路由]()
+ * [原生koa2实现路由](note/route/simple.md)
+ * [koa-router中间件](note/route/koa-router.md)
+* [请求数据获取]()
+ * [GET请求数据获取](note/request/get.md)
+ * [POST请求数据获取](note/request/post.md)
+ * [koa-bodyparser中间件](note/request/post-use-middleware.md)
+* [静态资源加载]()
+ * [原生koa2实现静态资源服务器](note/static/server.md)
+ * [koa-static中间件](note/static/middleware.md)
+* [cookie/session]()
+ * [koa2使用cookie](note/cookie/info.md)
+ * [koa2实现session](note/session/info.md)
+* [模板引擎]()
+ * [koa2加载模板引擎](note/template/add.md)
+ * [ejs模板引擎](note/template/ejs.md)
+* [文件上传]()
+ * [busboy模块](note/upload/busboy.md)
+ * [上传文件简单实现](note/upload/simple.md)
+* [数据库mysql]()
+ * [mysql模块](note/mysql/info.md)
+ * [async/await封装使用mysql](note/mysql/async.md)
+ * [项目建表初始化](note/mysql/init.md)
+* [项目框架搭建]()
+ * [框架设计](note/project/framework.md)
+ * [分层操作](note/project/layer.md)
+ * [数据库设计](note/project/sql.md)
+ * [路由设计](note/project/route.md)
+ * [webpack2环境搭建](note/project/webpack2.md)
+ * [使用react.js](note/project/react.md)
+ * [登录注册功能实现](note/project/sign.md)
+ * [session登录态判断处理](note/project/session.md)
+* [debug]()
+ * [开发debug](README.md)
diff --git a/demo/cookie/index.js b/demo/cookie/index.js
new file mode 100644
index 0000000..fcb2391
--- /dev/null
+++ b/demo/cookie/index.js
@@ -0,0 +1,28 @@
+const Koa = require('koa')
+const app = new Koa()
+
+app.use( async ( ctx ) => {
+
+ if ( ctx.url === '/index' ) {
+ // 设置cookie
+ ctx.cookies.set(
+ 'cid',
+ 'hello world',
+ {
+ domain: 'localhost', // 写cookie所在的域名
+ path: '/index', // 写cookie所在的路径
+ maxAge: 10 * 60 * 1000, // cookie有效时长
+ expires: new Date('2017-02-15'), // cookie失效时间
+ httpOnly: false, // 是否只用于http请求中获取
+ overwrite: false // 是否允许重写
+ }
+ )
+ ctx.body = 'cookie is ok'
+ } else {
+ ctx.body = 'hello world'
+ }
+
+})
+
+app.listen(3000)
+console.log('[demo] cookie is starting at port 3000')
diff --git a/demo/cookie/package.json b/demo/cookie/package.json
new file mode 100644
index 0000000..de32741
--- /dev/null
+++ b/demo/cookie/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "cookie",
+ "version": "1.0.0",
+ "description": "koa cookie demo",
+ "main": "index.js",
+ "scripts": {
+ "start": "node --harmony index.js"
+ },
+ "keywords": [
+ "koajs"
+ ],
+ "author": "chenshenhai",
+ "license": "MIT",
+ "dependencies": {
+ "koa": "^2.0.0-alpha.8"
+ },
+ "engines": {
+ "node": ">=7.0.0",
+ "npm": "~3.0.0"
+ }
+}
diff --git a/demo/ejs/index.js b/demo/ejs/index.js
new file mode 100644
index 0000000..6bb9c55
--- /dev/null
+++ b/demo/ejs/index.js
@@ -0,0 +1,18 @@
+const Koa = require('koa')
+const views = require('koa-views')
+const path = require('path')
+const app = new Koa()
+
+app.use(views(path.join(__dirname, './view'), {
+ extension: 'ejs'
+}))
+
+app.use( async ( ctx ) => {
+ let title = 'hello koa2'
+ await ctx.render('index', {
+ title,
+ })
+})
+
+app.listen(3000)
+console.log('[demo] ejs is starting at port 3000')
diff --git a/demo/ejs/package.json b/demo/ejs/package.json
new file mode 100644
index 0000000..bb089f6
--- /dev/null
+++ b/demo/ejs/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "ejs",
+ "version": "1.0.0",
+ "description": "koa ejs demo",
+ "main": "index.js",
+ "scripts": {
+ "start": "node --harmony index.js"
+ },
+ "keywords": [
+ "koajs"
+ ],
+ "author": "chenshenhai",
+ "license": "MIT",
+ "dependencies": {
+ "koa": "^2.0.0-alpha.8"
+ },
+ "engines": {
+ "node": ">=7.0.0",
+ "npm": "~3.0.0"
+ }
+}
diff --git a/demo/ejs/view/index.ejs b/demo/ejs/view/index.ejs
new file mode 100644
index 0000000..d629b2e
--- /dev/null
+++ b/demo/ejs/view/index.ejs
@@ -0,0 +1,10 @@
+
+
+
+ <%= title %>
+
+
+ <%= title %>
+ EJS Welcome to <%= title %>
+
+
\ No newline at end of file
diff --git a/demo/mysql/index.js b/demo/mysql/index.js
new file mode 100644
index 0000000..2ece008
--- /dev/null
+++ b/demo/mysql/index.js
@@ -0,0 +1,41 @@
+
+const fs = require('fs');
+const getSqlContentMap = require('./util/get-sql-content-map');
+const { query } = require('./util/db');
+
+
+// 打印脚本执行日志
+const eventLog = function( err , sqlFile, index ) {
+ if( err ) {
+ console.log(`[ERROR] sql脚本文件: ${sqlFile} 第${index + 1}条脚本 执行失败 o(╯□╰)o !`)
+ } else {
+ console.log(`[SUCCESS] sql脚本文件: ${sqlFile} 第${index + 1}条脚本 执行成功 O(∩_∩)O !`)
+ }
+}
+
+// 获取所有sql脚本内容
+let sqlContentMap = getSqlContentMap()
+
+// 执行建表sql脚本
+const createAllTables = async () => {
+ for( let key in sqlContentMap ) {
+ let sqlShell = sqlContentMap[key]
+ let sqlShellList = sqlShell.split(';')
+
+ for ( let [ i, shell ] of sqlShellList.entries() ) {
+ if ( shell.trim() ) {
+ let result = await query( shell )
+ if ( result.serverStatus * 1 === 2 ) {
+ eventLog( null, key, i)
+ } else {
+ eventLog( true, key, i)
+ }
+ }
+ }
+ }
+ console.log('sql脚本执行结束!')
+ console.log('请按 ctrl + c 键退出!')
+
+}
+
+createAllTables();
\ No newline at end of file
diff --git a/demo/mysql/package.json b/demo/mysql/package.json
new file mode 100644
index 0000000..c363e0e
--- /dev/null
+++ b/demo/mysql/package.json
@@ -0,0 +1,22 @@
+{
+ "name": "mysql",
+ "version": "1.0.0",
+ "description": "koa start demo",
+ "main": "index.js",
+ "scripts": {
+ "start": "node --harmony index.js"
+ },
+ "keywords": [
+ "koajs"
+ ],
+ "author": "chenshenhai",
+ "license": "MIT",
+ "dependencies": {
+ "koa": "^2.0.0-alpha.8",
+ "mysql": "^2.13.0"
+ },
+ "engines": {
+ "node": ">=7.0.0",
+ "npm": "~3.0.0"
+ }
+}
diff --git a/demo/mysql/sql/data.sql b/demo/mysql/sql/data.sql
new file mode 100644
index 0000000..9695f0c
--- /dev/null
+++ b/demo/mysql/sql/data.sql
@@ -0,0 +1,9 @@
+CREATE TABLE IF NOT EXISTS `data` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `data_info` json DEFAULT NULL,
+ `create_time` varchar(20) DEFAULT NULL,
+ `modified_time` varchar(20) DEFAULT NULL,
+ `level` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8
+
diff --git a/demo/mysql/sql/user.sql b/demo/mysql/sql/user.sql
new file mode 100644
index 0000000..be5ed2d
--- /dev/null
+++ b/demo/mysql/sql/user.sql
@@ -0,0 +1,16 @@
+CREATE TABLE IF NOT EXISTS `user` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `email` varchar(255) DEFAULT NULL,
+ `password` varchar(255) DEFAULT NULL,
+ `name` varchar(255) DEFAULT NULL,
+ `nick` varchar(255) DEFAULT NULL,
+ `detail_info` json DEFAULT NULL,
+ `create_time` varchar(20) DEFAULT NULL,
+ `modified_time` varchar(20) DEFAULT NULL,
+ `level` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+INSERT INTO `user` set email='1@example.com', password='123456';
+INSERT INTO `user` set email='2@example.com', password='123456';
+INSERT INTO `user` set email='3@example.com', password='123456';
diff --git a/demo/mysql/util/db.js b/demo/mysql/util/db.js
new file mode 100644
index 0000000..7dd6fb9
--- /dev/null
+++ b/demo/mysql/util/db.js
@@ -0,0 +1,35 @@
+const mysql = require('mysql')
+
+const pool = mysql.createPool({
+ host : '127.0.0.1',
+ user : 'root',
+ password : 'abc123',
+ database : 'koa_demo'
+})
+
+let query = function( sql, values ) {
+
+ return new Promise(( resolve, reject ) => {
+ pool.getConnection(function(err, connection) {
+ if (err) {
+ resolve( err )
+ } else {
+ connection.query(sql, values, ( err, rows) => {
+
+ if ( err ) {
+ reject( err )
+ } else {
+ resolve( rows )
+ }
+ connection.release()
+ })
+ }
+ })
+ })
+
+}
+
+
+module.exports = {
+ query
+}
\ No newline at end of file
diff --git a/demo/mysql/util/get-sql-content-map.js b/demo/mysql/util/get-sql-content-map.js
new file mode 100644
index 0000000..f18e7e4
--- /dev/null
+++ b/demo/mysql/util/get-sql-content-map.js
@@ -0,0 +1,30 @@
+const fs = require('fs')
+const getSqlMap = require('./get-sql-map')
+
+let sqlContentMap = {}
+
+/**
+ * 读取sql文件内容
+ * @param {string} fileName 文件名称
+ * @param {string} path 文件所在的路径
+ * @return {string} 脚本文件内容
+ */
+function getSqlContent( fileName, path ) {
+ let content = fs.readFileSync( path, 'binary' )
+ sqlContentMap[ fileName ] = content
+}
+
+/**
+ * 封装所有sql文件脚本内容
+ * @return {object}
+ */
+function getSqlContentMap () {
+ let sqlMap = getSqlMap()
+ for( let key in sqlMap ) {
+ getSqlContent( key, sqlMap[key] )
+ }
+
+ return sqlContentMap
+}
+
+module.exports = getSqlContentMap
\ No newline at end of file
diff --git a/demo/mysql/util/get-sql-map.js b/demo/mysql/util/get-sql-map.js
new file mode 100644
index 0000000..e2b0249
--- /dev/null
+++ b/demo/mysql/util/get-sql-map.js
@@ -0,0 +1,20 @@
+const fs = require('fs')
+const walkFile = require('./walk-file')
+
+/**
+ * 获取sql目录下的文件目录数据
+ * @return {object}
+ */
+function getSqlMap () {
+ let basePath = __dirname
+ basePath = basePath.replace(/\\/g, '\/')
+
+ let pathArr = basePath.split('\/')
+ pathArr = pathArr.splice( 0, pathArr.length - 1 )
+ basePath = pathArr.join('/') + '/sql/'
+
+ let fileList = walkFile( basePath, 'sql' )
+ return fileList
+}
+
+module.exports = getSqlMap
\ No newline at end of file
diff --git a/demo/mysql/util/walk-file.js b/demo/mysql/util/walk-file.js
new file mode 100644
index 0000000..1399e70
--- /dev/null
+++ b/demo/mysql/util/walk-file.js
@@ -0,0 +1,28 @@
+const fs = require('fs')
+
+/**
+ * 遍历目录下的文件目录
+ * @param {string} pathResolve 需进行遍历的目录路径
+ * @param {string} mime 遍历文件的后缀名
+ * @return {object} 返回遍历后的目录结果
+ */
+const walkFile = function( pathResolve , mime ){
+
+ let files = fs.readdirSync( pathResolve )
+
+ let fileList = {}
+
+ for( let [ i, item] of files.entries() ) {
+ let itemArr = item.split('\.')
+
+ let itemMime = ( itemArr.length > 1 ) ? itemArr[ itemArr.length - 1 ] : 'undefined'
+ let keyName = item + ''
+ if( mime === itemMime ) {
+ fileList[ item ] = pathResolve + item
+ }
+ }
+
+ return fileList
+}
+
+module.exports = walkFile
\ No newline at end of file
diff --git a/demo/project/.babelrc b/demo/project/.babelrc
new file mode 100644
index 0000000..9ab1939
--- /dev/null
+++ b/demo/project/.babelrc
@@ -0,0 +1,7 @@
+{
+ "presets": [["es2015", { "modules": false }], "react", "stage-2"],
+ "plugins": [
+ "transform-runtime",
+ ["import", { "libraryName": "antd", "style": "css" }]
+ ]
+}
diff --git a/demo/project/.editorconfig b/demo/project/.editorconfig
new file mode 100644
index 0000000..646d5c8
--- /dev/null
+++ b/demo/project/.editorconfig
@@ -0,0 +1,23 @@
+[*]
+charset=utf-8
+end_of_line=lf
+insert_final_newline=false
+indent_style=space
+indent_size=2
+
+[{.eslintrc,.babelrc,.stylelintrc,*.json,*.jsb3,*.jsb2,*.bowerrc}]
+indent_style=space
+indent_size=2
+
+[*.less]
+indent_style=space
+indent_size=2
+
+[*.scss]
+indent_style=space
+indent_size=2
+
+[{.analysis_options,*.yml,*.yaml}]
+indent_style=space
+indent_size=2
+
diff --git a/demo/project/.gitignore b/demo/project/.gitignore
new file mode 100644
index 0000000..61106bf
--- /dev/null
+++ b/demo/project/.gitignore
@@ -0,0 +1,7 @@
+node_modules/
+.idea/
+.DS_Store
+static/output/dist/
+static/output/upload/
+npm-debug.log
+npm-debug.log.*
diff --git a/demo/project/config.js b/demo/project/config.js
new file mode 100644
index 0000000..26f0837
--- /dev/null
+++ b/demo/project/config.js
@@ -0,0 +1,14 @@
+const config = {
+
+ port: 3001,
+
+ database: {
+ DATABASE: 'koa_demo',
+ USERNAME: 'root',
+ PASSWORD: 'abc123',
+ PORT: '3306',
+ HOST: 'localhost'
+ }
+}
+
+module.exports = config
\ No newline at end of file
diff --git a/demo/project/init/index.js b/demo/project/init/index.js
new file mode 100644
index 0000000..2ece008
--- /dev/null
+++ b/demo/project/init/index.js
@@ -0,0 +1,41 @@
+
+const fs = require('fs');
+const getSqlContentMap = require('./util/get-sql-content-map');
+const { query } = require('./util/db');
+
+
+// 打印脚本执行日志
+const eventLog = function( err , sqlFile, index ) {
+ if( err ) {
+ console.log(`[ERROR] sql脚本文件: ${sqlFile} 第${index + 1}条脚本 执行失败 o(╯□╰)o !`)
+ } else {
+ console.log(`[SUCCESS] sql脚本文件: ${sqlFile} 第${index + 1}条脚本 执行成功 O(∩_∩)O !`)
+ }
+}
+
+// 获取所有sql脚本内容
+let sqlContentMap = getSqlContentMap()
+
+// 执行建表sql脚本
+const createAllTables = async () => {
+ for( let key in sqlContentMap ) {
+ let sqlShell = sqlContentMap[key]
+ let sqlShellList = sqlShell.split(';')
+
+ for ( let [ i, shell ] of sqlShellList.entries() ) {
+ if ( shell.trim() ) {
+ let result = await query( shell )
+ if ( result.serverStatus * 1 === 2 ) {
+ eventLog( null, key, i)
+ } else {
+ eventLog( true, key, i)
+ }
+ }
+ }
+ }
+ console.log('sql脚本执行结束!')
+ console.log('请按 ctrl + c 键退出!')
+
+}
+
+createAllTables();
\ No newline at end of file
diff --git a/demo/project/init/sql/user_info.sql b/demo/project/init/sql/user_info.sql
new file mode 100644
index 0000000..1d81867
--- /dev/null
+++ b/demo/project/init/sql/user_info.sql
@@ -0,0 +1,14 @@
+CREATE TABLE IF NOT EXISTS `user_info` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `email` varchar(255) DEFAULT NULL,
+ `password` varchar(255) DEFAULT NULL,
+ `name` varchar(255) DEFAULT NULL,
+ `nick` varchar(255) DEFAULT NULL,
+ `detail_info` longtext DEFAULT NULL,
+ `create_time` varchar(20) DEFAULT NULL,
+ `modified_time` varchar(20) DEFAULT NULL,
+ `level` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+INSERT INTO `user_info` set name='admin001', email='admin001@example.com', password='123456';
diff --git a/demo/project/init/util/db.js b/demo/project/init/util/db.js
new file mode 100644
index 0000000..947f448
--- /dev/null
+++ b/demo/project/init/util/db.js
@@ -0,0 +1,39 @@
+const mysql = require('mysql')
+const config = require('./../../config')
+const dbConfig = config.database
+
+const pool = mysql.createPool({
+ host : dbConfig.HOST,
+ user : dbConfig.USERNAME,
+ password : dbConfig.PASSWORD,
+ database : dbConfig.DATABASE
+})
+
+
+let query = function( sql, values ) {
+
+ return new Promise(( resolve, reject ) => {
+ pool.getConnection(function(err, connection) {
+ if (err) {
+ console.log( err )
+ resolve( err )
+ } else {
+ connection.query(sql, values, ( err, rows) => {
+ if ( err ) {
+ console.log( err )
+ reject( err )
+ } else {
+ resolve( rows )
+ }
+ connection.release()
+ })
+ }
+ })
+ })
+
+}
+
+
+module.exports = {
+ query
+}
\ No newline at end of file
diff --git a/demo/project/init/util/get-sql-content-map.js b/demo/project/init/util/get-sql-content-map.js
new file mode 100644
index 0000000..f18e7e4
--- /dev/null
+++ b/demo/project/init/util/get-sql-content-map.js
@@ -0,0 +1,30 @@
+const fs = require('fs')
+const getSqlMap = require('./get-sql-map')
+
+let sqlContentMap = {}
+
+/**
+ * 读取sql文件内容
+ * @param {string} fileName 文件名称
+ * @param {string} path 文件所在的路径
+ * @return {string} 脚本文件内容
+ */
+function getSqlContent( fileName, path ) {
+ let content = fs.readFileSync( path, 'binary' )
+ sqlContentMap[ fileName ] = content
+}
+
+/**
+ * 封装所有sql文件脚本内容
+ * @return {object}
+ */
+function getSqlContentMap () {
+ let sqlMap = getSqlMap()
+ for( let key in sqlMap ) {
+ getSqlContent( key, sqlMap[key] )
+ }
+
+ return sqlContentMap
+}
+
+module.exports = getSqlContentMap
\ No newline at end of file
diff --git a/demo/project/init/util/get-sql-map.js b/demo/project/init/util/get-sql-map.js
new file mode 100644
index 0000000..e2b0249
--- /dev/null
+++ b/demo/project/init/util/get-sql-map.js
@@ -0,0 +1,20 @@
+const fs = require('fs')
+const walkFile = require('./walk-file')
+
+/**
+ * 获取sql目录下的文件目录数据
+ * @return {object}
+ */
+function getSqlMap () {
+ let basePath = __dirname
+ basePath = basePath.replace(/\\/g, '\/')
+
+ let pathArr = basePath.split('\/')
+ pathArr = pathArr.splice( 0, pathArr.length - 1 )
+ basePath = pathArr.join('/') + '/sql/'
+
+ let fileList = walkFile( basePath, 'sql' )
+ return fileList
+}
+
+module.exports = getSqlMap
\ No newline at end of file
diff --git a/demo/project/init/util/walk-file.js b/demo/project/init/util/walk-file.js
new file mode 100644
index 0000000..1399e70
--- /dev/null
+++ b/demo/project/init/util/walk-file.js
@@ -0,0 +1,28 @@
+const fs = require('fs')
+
+/**
+ * 遍历目录下的文件目录
+ * @param {string} pathResolve 需进行遍历的目录路径
+ * @param {string} mime 遍历文件的后缀名
+ * @return {object} 返回遍历后的目录结果
+ */
+const walkFile = function( pathResolve , mime ){
+
+ let files = fs.readdirSync( pathResolve )
+
+ let fileList = {}
+
+ for( let [ i, item] of files.entries() ) {
+ let itemArr = item.split('\.')
+
+ let itemMime = ( itemArr.length > 1 ) ? itemArr[ itemArr.length - 1 ] : 'undefined'
+ let keyName = item + ''
+ if( mime === itemMime ) {
+ fileList[ item ] = pathResolve + item
+ }
+ }
+
+ return fileList
+}
+
+module.exports = walkFile
\ No newline at end of file
diff --git a/demo/project/package.json b/demo/project/package.json
new file mode 100644
index 0000000..2eb79a9
--- /dev/null
+++ b/demo/project/package.json
@@ -0,0 +1,64 @@
+{
+ "name": "project",
+ "version": "0.0.1",
+ "description": "a koa2 project",
+ "main": "index.js",
+ "scripts": {
+ "init_sql": "node --harmony ./init/index.js",
+ "dev": "npm run dev_server & npm run dev_react",
+ "dev_server": "nodemon --harmony ./server/app",
+ "dev_static": "webpack --watch --config ./static/build/webpack.dev.config.js",
+ "start_static": "webpack --config ./static/build/webpack.prod.config.js",
+ "start_server": "node --harmony ./server/app.js",
+ "start": "npm run start_static & npm run start_server"
+ },
+ "author": "chenshenhai",
+ "license": "MIT",
+ "dependencies": {
+ "antd": "^2.7.2",
+ "busboy": "^0.2.14",
+ "co": "^4.6.0",
+ "debug": "^2.6.0",
+ "ejs": "^2.5.5",
+ "koa": "^2.0.0",
+ "koa-bodyparser": "^3.2.0",
+ "koa-convert": "^1.2.0",
+ "koa-logger": "^1.3.0",
+ "koa-mysql-session": "0.0.2",
+ "koa-router": "^7.0.1",
+ "koa-send": "^3.2.0",
+ "koa-session-minimal": "^3.0.3",
+ "koa-static": "^2.0.0",
+ "koa-views": "^5.2.0",
+ "mysql": "^2.12.0",
+ "react": "^15.4.1",
+ "react-dom": "^15.4.1",
+ "validator": "^6.2.0",
+ "whatwg-fetch": "^2.0.1"
+ },
+ "devDependencies": {
+ "babel-core": "^6.21.0",
+ "babel-loader": "^6.2.10",
+ "babel-plugin-import": "^1.1.1",
+ "babel-plugin-transform-runtime": "^6.15.0",
+ "babel-preset-es2015": "^6.18.0",
+ "babel-preset-react": "^6.16.0",
+ "babel-preset-stage-2": "^6.18.0",
+ "babel-runtime": "^6.20.0",
+ "css-loader": "^0.26.1",
+ "extract-text-webpack-plugin": "^2.0.0-beta.4",
+ "jsx-loader": "^0.13.2",
+ "less": "^2.7.1",
+ "less-loader": "^2.2.3",
+ "node-sass": "^4.1.1",
+ "nodemon": "^1.11.0",
+ "sass-loader": "^4.1.1",
+ "style-loader": "^0.13.1",
+ "webpack": "^2.2.0-rc.3",
+ "webpack-merge": "^2.0.0"
+ },
+ "engines": {
+ "node": ">= 7.0.0",
+ "npm": ">= 3.0.0"
+ }
+}
diff --git a/demo/project/server/app.js b/demo/project/server/app.js
new file mode 100644
index 0000000..f42633c
--- /dev/null
+++ b/demo/project/server/app.js
@@ -0,0 +1,51 @@
+const path = require('path')
+const Koa = require('koa')
+const convert = require('koa-convert')
+const views = require('koa-views')
+const koaStatic = require('koa-static')
+const bodyParser = require('koa-bodyparser')
+const koaLogger = require('koa-logger')
+const session = require('koa-session-minimal')
+const MysqlStore = require('koa-mysql-session')
+
+const config = require('./../config')
+const routers = require('./routers/index')
+
+const app = new Koa()
+
+// session存储配置
+const sessionMysqlConfig= {
+ user: config.database.USERNAME,
+ password: config.database.PASSWORD,
+ database: config.database.DATABASE,
+ host: config.database.HOST,
+}
+
+// 配置session中间件
+app.use(session({
+ key: 'USER_SID',
+ store: new MysqlStore(sessionMysqlConfig)
+}))
+
+// 配置控制台日志中间件
+app.use(convert(koaLogger()))
+
+// 配置ctx.body解析中间件
+app.use(bodyParser())
+
+// 配置静态资源加载中间件
+app.use(convert(koaStatic(
+ path.join(__dirname , './../static')
+)))
+
+// 配置服务端模板渲染引擎中间件
+app.use(views(path.join(__dirname, './views'), {
+ extension: 'ejs'
+}))
+
+// 初始化路由中间件
+app.use(routers.routes()).use(routers.allowedMethods())
+
+// 监听启动端口
+app.listen( config.port )
+console.log(`the server is start at port ${config.port}`)
diff --git a/demo/project/server/codes/user.js b/demo/project/server/codes/user.js
new file mode 100644
index 0000000..1c17157
--- /dev/null
+++ b/demo/project/server/codes/user.js
@@ -0,0 +1,30 @@
+/**
+ * 逻辑文案管理
+ */
+
+const userCode = {
+
+ ERROR_USER_NAME: '用户名格式为6-16位的小写字母,包括-、_',
+
+ ERROR_EMAIL: '请输入正确的邮箱地址',
+
+ ERROR_PASSWORD: '密码长度应该为6-16',
+
+ ERROR_PASSWORD_CONFORM: '两次密码不一致',
+
+ ERROR_SYS: '系统错误',
+
+ FAIL_EMAIL_IS_EXIST: '邮箱已被注册',
+
+ FAIL_USER_NAME_IS_EXIST: '用户名已被注册',
+
+ FAIL_USER_NAME_OR_PASSWORD_ERROR: '用户名或登录密码错误',
+
+ FAIL_USER_NO_LOGIN: '用户未登录',
+
+ FAIL_USER_NO_EXIST: '用户不存在',
+
+}
+
+
+module.exports = userCode
diff --git a/demo/project/server/controllers/admin.js b/demo/project/server/controllers/admin.js
new file mode 100644
index 0000000..f76eb22
--- /dev/null
+++ b/demo/project/server/controllers/admin.js
@@ -0,0 +1,10 @@
+module.exports = {
+
+ async indexPage ( ctx ) {
+ const title = 'admin page'
+ await ctx.render('admin', {
+ title,
+ })
+ },
+
+}
\ No newline at end of file
diff --git a/demo/project/server/controllers/index.js b/demo/project/server/controllers/index.js
new file mode 100644
index 0000000..4943fa0
--- /dev/null
+++ b/demo/project/server/controllers/index.js
@@ -0,0 +1,6 @@
+module.exports = async ( ctx ) => {
+ const title = 'home'
+ await ctx.render('index', {
+ title
+ })
+}
\ No newline at end of file
diff --git a/demo/project/server/controllers/user-info.js b/demo/project/server/controllers/user-info.js
new file mode 100644
index 0000000..d35b1ba
--- /dev/null
+++ b/demo/project/server/controllers/user-info.js
@@ -0,0 +1,153 @@
+const userInfoService = require('./../services/user-info')
+const userCode = require('./../codes/user')
+
+module.exports = {
+
+ /**
+ * 登录操作
+ * @param {obejct} ctx 上下文对象
+ */
+ async signIn( ctx ) {
+ let formData = ctx.request.body
+ let result = {
+ success: false,
+ message: '',
+ data: null,
+ code: ''
+ }
+
+ let userResult = await userInfoService.signIn( formData )
+
+ if ( userResult ) {
+ if ( formData.userName === userResult.name ) {
+ result.success = true
+ } else {
+ result.message = userCode.FAIL_USER_NAME_OR_PASSWORD_ERROR
+ result.code = 'FAIL_USER_NAME_OR_PASSWORD_ERROR'
+ }
+ } else {
+ result.code = 'FAIL_USER_NO_EXIST',
+ result.message = userCode.FAIL_USER_NO_EXIST
+ }
+
+ if ( formData.source === 'form' && result.success === true ) {
+ let session = ctx.session
+ session.isLogin = true
+ session.userName = userResult.name
+ session.userId = userResult.id
+
+ ctx.redirect('/work')
+ } else {
+ ctx.body = result
+ }
+ },
+
+ /**
+ * 注册操作
+ * @param {obejct} ctx 上下文对象
+ */
+ async signUp( ctx ) {
+ let formData = ctx.request.body
+ let result = {
+ success: false,
+ message: '',
+ data: null
+ }
+
+ let validateResult = userInfoService.validatorSignUp( formData )
+
+ if ( validateResult.success === false ) {
+ result = validateResult
+ ctx.body = result
+ return
+ }
+
+ let existOne = await userInfoService.getExistOne(formData)
+ console.log( existOne )
+
+ if ( existOne ) {
+ if ( existOne .name === formData.userName ) {
+ result.message = userCode.FAIL_USER_NAME_IS_EXIST
+ ctx.body = result
+ return
+ }
+ if ( existOne .email === formData.email ) {
+ result.message = userCode.FAIL_EMAIL_IS_EXIST
+ ctx.body = result
+ return
+ }
+ }
+
+
+ let userResult = await userInfoService.create({
+ email: formData.email,
+ password: formData.password,
+ name: formData.userName,
+ create_time: new Date().getTime(),
+ level: 1,
+ })
+
+ console.log( userResult )
+
+ if ( userResult && userResult.insertId * 1 > 0) {
+ result.success = true
+ } else {
+ result.message = userCode.ERROR_SYS
+ }
+
+ ctx.body = result
+ },
+
+ /**
+ * 获取用户信息
+ * @param {obejct} ctx 上下文对象
+ */
+ async getLoginUserInfo( ctx ) {
+ let session = ctx.session
+ let isLogin = session.isLogin
+ let userName = session.userName
+
+ console.log( 'session=', session )
+
+ let result = {
+ success: false,
+ message: '',
+ data: null,
+ }
+ if ( isLogin === true && userName ) {
+ let userInfo = await userInfoService.getUserInfoByUserName( userName )
+ if ( userInfo ) {
+ result.data = userInfo
+ result.success = true
+ } else {
+ result.message = userCode.FAIL_USER_NO_LOGIN
+ }
+ } else {
+ // TODO
+ }
+
+ ctx.body = result
+ },
+
+ /**
+ * 校验用户是否登录
+ * @param {obejct} ctx 上下文对象
+ */
+ validateLogin( ctx ) {
+ let result = {
+ success: false,
+ message: userCode.FAIL_USER_NO_LOGIN,
+ data: null,
+ code: 'FAIL_USER_NO_LOGIN',
+ }
+ let session = ctx.session
+ if( session && session.isLogin === true ) {
+ result.success = true
+ result.message = ''
+ result.code = ''
+ }
+ return result
+ }
+
+
+}
diff --git a/demo/project/server/controllers/work.js b/demo/project/server/controllers/work.js
new file mode 100644
index 0000000..7a43dd4
--- /dev/null
+++ b/demo/project/server/controllers/work.js
@@ -0,0 +1,16 @@
+module.exports = {
+
+ async indexPage ( ctx ) {
+ // 判断是否有session
+ if ( ctx.session && ctx.session.isLogin && ctx.session.userName ) {
+ const title = 'work页面'
+ await ctx.render('work', {
+ title,
+ })
+ } else {
+ // 没有登录态则跳转到错误页面
+ ctx.redirect('/error')
+ }
+ },
+
+}
\ No newline at end of file
diff --git a/demo/project/server/models/user-Info.js b/demo/project/server/models/user-Info.js
new file mode 100644
index 0000000..1577de7
--- /dev/null
+++ b/demo/project/server/models/user-Info.js
@@ -0,0 +1,76 @@
+const dbUtils = require('./../utils/db-util')
+
+const user = {
+
+ /**
+ * 数据库创建用户
+ * @param {object} model 用户数据模型
+ * @return {object} mysql执行结果
+ */
+ async create ( model ) {
+ let result = await dbUtils.insertData( 'user_info', model )
+ return result
+ },
+
+ /**
+ * 查找一个存在用户的数据
+ * @param {obejct} options 查找条件参数
+ * @return {object|null} 查找结果
+ */
+ async getExistOne(options ) {
+ let _sql = `
+ SELECT * from user_info
+ where email="${options.email}" or name="${options.name}"
+ limit 1`
+ let result = await dbUtils.query( _sql )
+ if ( Array.isArray(result) && result.length > 0 ) {
+ result = result[0]
+ } else {
+ result = null
+ }
+ return result
+ },
+
+ /**
+ * 根据用户名和密码查找用户
+ * @param {object} options 用户名密码对象
+ * @return {object|null} 查找结果
+ */
+ async getOneByUserNameAndPassword( options ) {
+ let _sql = `
+ SELECT * from user_info
+ where password="${options.password}" and name="${options.name}"
+ limit 1`
+ let result = await dbUtils.query( _sql )
+ if ( Array.isArray(result) && result.length > 0 ) {
+ result = result[0]
+ } else {
+ result = null
+ }
+ return result
+ },
+
+ /**
+ * 根据用户名查找用户信息
+ * @param {string} userName 用户账号名称
+ * @return {object|null} 查找结果
+ */
+ async getUserInfoByUserName( userName ) {
+
+ let result = await dbUtils.select(
+ 'user_info',
+ ['id', 'email', 'name', 'detail_info', 'create_time', 'modified_time', 'modified_time' ])
+ if ( Array.isArray(result) && result.length > 0 ) {
+ result = result[0]
+ } else {
+ result = null
+ }
+ return result
+ },
+
+
+
+}
+
+
+module.exports = user
diff --git a/demo/project/server/routers/admin.js b/demo/project/server/routers/admin.js
new file mode 100644
index 0000000..a9cb25f
--- /dev/null
+++ b/demo/project/server/routers/admin.js
@@ -0,0 +1,8 @@
+/**
+ * 管理员用户子路由
+ */
+
+const router = require('koa-router')()
+const admin = require('./../controllers/admin')
+
+module.exports = router.get( '/', admin.indexPage )
\ No newline at end of file
diff --git a/demo/project/server/routers/api.js b/demo/project/server/routers/api.js
new file mode 100644
index 0000000..0691edc
--- /dev/null
+++ b/demo/project/server/routers/api.js
@@ -0,0 +1,14 @@
+/**
+ * restful api 子路由
+ */
+
+const router = require('koa-router')()
+const userInfoController = require('./../controllers/user-info')
+
+const routers = router
+ .get('/user/getUserInfo.json', userInfoController.getLoginUserInfo)
+ .post('/user/signIn.json', userInfoController.signIn)
+ .post('/user/signUp.json', userInfoController.signUp)
+
+
+module.exports = routers
diff --git a/demo/project/server/routers/error.js b/demo/project/server/routers/error.js
new file mode 100644
index 0000000..439b893
--- /dev/null
+++ b/demo/project/server/routers/error.js
@@ -0,0 +1,12 @@
+/**
+ * 错误页面子路由
+ */
+
+const router = require('koa-router')()
+
+module.exports = router.get('*', async ( ctx ) => {
+ const title = 'error'
+ await ctx.render('error', {
+ title
+ })
+})
\ No newline at end of file
diff --git a/demo/project/server/routers/home.js b/demo/project/server/routers/home.js
new file mode 100644
index 0000000..0860e84
--- /dev/null
+++ b/demo/project/server/routers/home.js
@@ -0,0 +1,9 @@
+/**
+ * 主页子路由
+ */
+
+const router = require('koa-router')()
+const index = require('../controllers/index')
+
+module.exports = router
+ .get('/', index)
diff --git a/demo/project/server/routers/index.js b/demo/project/server/routers/index.js
new file mode 100644
index 0000000..9be7a2f
--- /dev/null
+++ b/demo/project/server/routers/index.js
@@ -0,0 +1,21 @@
+/**
+ * 整合所有子路由
+ */
+
+const router = require('koa-router')()
+
+const home = require('./home')
+const api = require('./api')
+const admin = require('./admin')
+const work = require('./work')
+const error = require('./error')
+
+router.use('/', home.routes(), home.allowedMethods())
+router.use('/api', api.routes(), api.allowedMethods())
+router.use('/admin', admin.routes(), admin.allowedMethods())
+router.use('/work', work.routes(), work.allowedMethods())
+router.use('/error', error.routes(), error.allowedMethods())
+
+module.exports = router
+
+
diff --git a/demo/project/server/routers/work.js b/demo/project/server/routers/work.js
new file mode 100644
index 0000000..26f21b4
--- /dev/null
+++ b/demo/project/server/routers/work.js
@@ -0,0 +1,12 @@
+/**
+ * 工作台子路由
+ */
+
+const router = require('koa-router')()
+const controller = require('./../controllers/work')
+
+const routers = router
+ .get('/', controller.indexPage)
+
+
+module.exports = routers
\ No newline at end of file
diff --git a/demo/project/server/services/user-info.js b/demo/project/server/services/user-info.js
new file mode 100644
index 0000000..0c2a6c5
--- /dev/null
+++ b/demo/project/server/services/user-info.js
@@ -0,0 +1,101 @@
+/**
+ * 用户业务操作
+ */
+
+const validator = require('validator')
+const userModel = require('./../models/user-info')
+const userCode = require('./../codes/user')
+
+const user = {
+
+ /**
+ * 创建用户
+ * @param {object} user 用户信息
+ * @return {object} 创建结果
+ */
+ async create( user ) {
+ let result = await userModel.create(user)
+ return result
+ },
+
+ /**
+ * 查找存在用户信息
+ * @param {object} formData 查找的表单数据
+ * @return {object|null} 查找结果
+ */
+ async getExistOne( formData ) {
+ let resultData = await userModel.getExistOne({
+ 'email': formData.email,
+ 'name': formData.userName
+ })
+ return resultData
+ },
+
+ /**
+ * 登录业务操作
+ * @param {object} formData 登录表单信息
+ * @return {object} 登录业务操作结果
+ */
+ async signIn( formData ) {
+ let resultData = await userModel.getOneByUserNameAndPassword({
+ 'password': formData.password,
+ 'name': formData.userName})
+ return resultData
+ },
+
+
+ /**
+ * 根据用户名查找用户业务操作
+ * @param {string} userName 用户名
+ * @return {object|null} 查找结果
+ */
+ async getUserInfoByUserName( userName ) {
+
+ let resultData = await userModel.getUserInfoByUserName( userName ) || {}
+ let userInfo = {
+ // id: resultData.id,
+ email: resultData.email,
+ userName: resultData.name,
+ detailInfo: resultData.detail_info,
+ createTime: resultData.create_time
+ }
+ return userInfo
+ },
+
+
+ /**
+ * 检验用户注册数据
+ * @param {object} userInfo 用户注册数据
+ * @return {object} 校验结果
+ */
+ validatorSignUp( userInfo ) {
+ let result = {
+ success: false,
+ message: '',
+ }
+
+ if ( /[a-z0-9\_\-]{6,16}/.test(userInfo.userName) === false ) {
+ result.message = userCode.ERROR_USER_NAME
+ return result
+ }
+ if ( !validator.isEmail( userInfo.email ) ) {
+ result.message = userCode.ERROR_EMAIL
+ return result
+ }
+ if ( !/[\w+]{6,16}/.test( userInfo.password ) ) {
+ result.message = userCode.ERROR_PASSWORD
+ return result
+ }
+ if ( userInfo.password !== userInfo.confirmPassword ) {
+ result.message = userCode.ERROR_PASSWORD_CONFORM
+ return result
+ }
+
+ result.success = true
+
+ return result
+ }
+
+}
+
+module.exports = user
diff --git a/demo/project/server/utils/datetime.js b/demo/project/server/utils/datetime.js
new file mode 100644
index 0000000..bb935f3
--- /dev/null
+++ b/demo/project/server/utils/datetime.js
@@ -0,0 +1,66 @@
+const monthEnum = [
+ '01','02','03','04','05','06',
+ '07','08','09','10','11','12',
+]
+
+const dayEnum = [
+ '01','02','03','04','05','06','07','08','09','10',
+ '11','12','13','14','15','16','17','18','19','20',
+ '21','22','23','04','25','26','27','28','29','30', '31',
+]
+
+const timeEnum = [
+ '00',
+ '01','02','03','04','05','06','07','08','09','10',
+ '11','12','13','14','15','16','17','18','19','20',
+ '21','22','23','04','25','26','27','28','29','30',
+ '31','32','33','34','35','36','37','38','39','40',
+ '41','42','43','44','45','46','47','48','49','50',
+ '51','52','53','54','55','56','57','58','59',
+]
+
+const datatime = {
+
+ parseStampToFormat( timestamp, type ) {
+ let _date
+ if ( timestamp ) {
+ _date = new Date(timestamp)
+ } else {
+ _date = new Date()
+ }
+
+ let parsedDate
+ let parseTime
+ let parseDatetime
+
+ let yearNum = _date.getFullYear()
+ let monthNum = monthEnum[_date.getMonth()]
+ let dayNum = dayEnum[_date.getDate()-1]
+ let hourNum = timeEnum[_date.getHours()]
+ let minNum = timeEnum[_date.getMinutes()]
+ let secNum = timeEnum[_date.getSeconds()]
+
+ type = type || 'YYYY/MM/DD/hh/mm/ss'
+
+ parseDatetime = type
+ .replace('YYYY', yearNum)
+ .replace('MM', monthNum)
+ .replace('DD', dayNum)
+ .replace('hh', hourNum)
+ .replace('mm', minNum)
+ .replace('ss', secNum)
+
+ return parseDatetime
+ },
+
+ getNowDatetime() {
+ let timestamp = new Date().getTime()
+ let nowDatetime = this.parseStampToFormat( timestamp )
+ return nowDatetime
+ },
+
+
+
+}
+
+module.exports = datatime
\ No newline at end of file
diff --git a/demo/project/server/utils/db-util.js b/demo/project/server/utils/db-util.js
new file mode 100644
index 0000000..eb2a15d
--- /dev/null
+++ b/demo/project/server/utils/db-util.js
@@ -0,0 +1,89 @@
+const allConfig = require("./../../config")
+const config = allConfig.database
+const mysql = require("mysql")
+
+const pool = mysql.createPool({
+ host : config.HOST,
+ user : config.USERNAME,
+ password : config.PASSWORD,
+ database : config.DATABASE
+})
+
+let query = function( sql, values ) {
+
+ return new Promise(( resolve, reject ) => {
+ pool.getConnection(function(err, connection) {
+ if (err) {
+ resolve( err )
+ } else {
+ connection.query(sql, values, ( err, rows) => {
+
+ if ( err ) {
+ reject( err )
+ } else {
+ resolve( rows )
+ }
+ connection.release()
+ })
+ }
+ })
+ })
+
+}
+
+let createTable = function( sql ) {
+ return query( sql, [] )
+}
+
+
+let findDataById = function( table, id ) {
+ let _sql = "SELECT * FROM ?? WHERE id = ? "
+ return query( _sql, [ table, id, start, end ] )
+}
+
+
+let findDataByPage = function( table, keys, start, end ) {
+ let _sql = "SELECT ?? FROM ?? LIMIT ? , ?"
+ return query( _sql, [keys, table, start, end ] )
+}
+
+
+let insertData = function( table, values ) {
+ let _sql = "INSERT INTO ?? SET ?"
+ return query( _sql, [ table, values ] )
+}
+
+
+let updateData = function( table, values, id ) {
+ let _sql = "UPDATE ?? SET ? WHERE id = ?"
+ return query( _sql, [ table, values, id ] )
+}
+
+
+let deleteDataById = function( table, id ) {
+ let _sql = "DELETE FROM ?? WHERE id = ?"
+ return query( _sql, [ table, id ] )
+}
+
+
+let select = function( table, keys ) {
+ let _sql = "SELECT ?? FROM ?? "
+ return query( _sql, [ keys, table ] )
+}
+
+let count = function( table ) {
+ let _sql = "SELECT COUNT(*) AS total_count FROM ?? "
+ return query( _sql, [ table ] )
+}
+
+module.exports = {
+ query,
+ createTable,
+ findDataById,
+ findDataByPage,
+ deleteDataById,
+ insertData,
+ updateData,
+ select,
+ count,
+}
diff --git a/demo/project/server/utils/type.js b/demo/project/server/utils/type.js
new file mode 100644
index 0000000..35d31f3
--- /dev/null
+++ b/demo/project/server/utils/type.js
@@ -0,0 +1,37 @@
+const Types = {
+
+ isPrototype( data ) {
+ return Object.prototype.toString.call(data).toLowerCase()
+ },
+
+ isArray( data ) {
+ return this.isPrototype( data ) === '[object array]'
+ },
+
+ isJSON( data ) {
+ return this.isPrototype( data ) === '[object object]'
+ },
+
+ isFunction( data ) {
+ return this.isPrototype( data ) === '[object function]'
+ },
+
+ isString( data ) {
+ return this.isPrototype( data ) === '[object string]'
+ },
+
+ isNumber( data ) {
+ return this.isPrototype( data ) === '[object number]'
+ },
+
+ isUndefined( data ) {
+ return this.isPrototype( data ) === '[object undefined]'
+ },
+
+ isNull( data ) {
+ return this.isPrototype( data ) === '[object null]'
+ }
+
+}
+
+module.exports = Types
diff --git a/demo/project/server/utils/upload.js b/demo/project/server/utils/upload.js
new file mode 100644
index 0000000..60377fc
--- /dev/null
+++ b/demo/project/server/utils/upload.js
@@ -0,0 +1,102 @@
+const inspect = require('util').inspect
+const path = require('path')
+const os = require('os')
+const fs = require('fs')
+const Busboy = require('busboy')
+const UtilType = require('./type')
+const UtilDatetime = require('./datetime')
+
+
+function mkdirsSync(dirname) {
+ // console.log(dirname)
+ if (fs.existsSync(dirname)) {
+ return true
+ } else {
+ if (mkdirsSync(path.dirname(dirname))) {
+ fs.mkdirSync(dirname)
+ return true
+ }
+ }
+}
+
+function getSuffixName( fileName ) {
+ let nameList = fileName.split('.')
+ return nameList[nameList.length - 1]
+}
+
+function uploadPicture( ctx, options) {
+ let req = ctx.req
+ let res = ctx.res
+ let busboy = new Busboy({headers: req.headers})
+
+ let pictureType = 'common'
+ if ( UtilType.isJSON( options ) && UtilType.isString( options.pictureType ) ) {
+ pictureType = options.pictureType
+ }
+
+ let picturePath = path.join(
+ __dirname,
+ '/../../static/output/upload/',
+ pictureType,
+ UtilDatetime.parseStampToFormat(null, 'YYYY/MM/DD'))
+
+ console.log( path.sep, picturePath )
+ let mkdirResult = mkdirsSync( picturePath )
+
+
+ return new Promise((resolve, reject) => {
+ let result = {
+ success: false,
+ code: '',
+ message: '',
+ data: null
+ }
+
+ busboy.on('file', function(fieldname, file, filename, encoding, mimetype) {
+ console.log('File-file [' + fieldname + ']: filename: ' + filename + ', encoding: ' + encoding + ', mimetype: ' + mimetype)
+
+
+ let pictureName = Math.random().toString(16).substr(2) + '.' + getSuffixName(filename)
+ let _uploadFilePath = path.join( picturePath, pictureName )
+ console.log(_uploadFilePath)
+
+ let saveTo = path.join(_uploadFilePath)
+ file.pipe(fs.createWriteStream(saveTo))
+
+ // file.on('data', function(data) {
+ // console.log('File-data [' + fieldname + '] got ' + data.length + ' bytes')
+ // })
+
+ file.on('end', function() {
+ console.log('File-end [' + fieldname + '] Finished')
+ result.success = true
+ resolve(result)
+ })
+ })
+
+ // busboy.on('field', function(fieldname, val, fieldnameTruncated, valTruncated, encoding, mimetype) {
+ // console.log('Field-field [' + fieldname + ']: value: ' + inspect(val))
+ // })
+ // busboy.on('finish', function() {
+ // console.log('Done parsing form!')
+ // })
+
+ busboy.on('error', function(err) {
+ console.log('File-error')
+ reject(result)
+ })
+
+ req.pipe(busboy)
+ })
+
+}
+
+
+module.exports = {
+ uploadPicture,
+}
+
+
+
+
+
diff --git a/demo/project/server/views/admin.ejs b/demo/project/server/views/admin.ejs
new file mode 100644
index 0000000..7c0fee6
--- /dev/null
+++ b/demo/project/server/views/admin.ejs
@@ -0,0 +1,12 @@
+
+
+
+ <%= title %>
+
+
+
+
+
+
+
+
diff --git a/demo/project/server/views/error.ejs b/demo/project/server/views/error.ejs
new file mode 100644
index 0000000..488e791
--- /dev/null
+++ b/demo/project/server/views/error.ejs
@@ -0,0 +1,12 @@
+
+
+
+ <%= title %>
+
+
+
+
+
+
+
+
diff --git a/demo/project/server/views/index.ejs b/demo/project/server/views/index.ejs
new file mode 100644
index 0000000..953c56c
--- /dev/null
+++ b/demo/project/server/views/index.ejs
@@ -0,0 +1,12 @@
+
+
+
+ <%= title %>
+
+
+
+
+
+
+
+
diff --git a/demo/project/server/views/work.ejs b/demo/project/server/views/work.ejs
new file mode 100644
index 0000000..fefea40
--- /dev/null
+++ b/demo/project/server/views/work.ejs
@@ -0,0 +1,13 @@
+
+
+
+ <%= title %>
+
+
+
+
+
+
+
+
+
diff --git a/demo/project/static/build/webpack.base.config.js b/demo/project/static/build/webpack.base.config.js
new file mode 100644
index 0000000..0462734
--- /dev/null
+++ b/demo/project/static/build/webpack.base.config.js
@@ -0,0 +1,75 @@
+const webpack = require('webpack');
+const ExtractTextPlugin = require('extract-text-webpack-plugin');
+const path = require('path');
+const sourcePath = path.join(__dirname, './static/src');
+const outputPath = path.join(__dirname, './../output/dist/');
+
+module.exports = {
+
+ entry: {
+ 'admin' : './static/src/pages/admin.js',
+ 'work' : './static/src/pages/work.js',
+ 'index' : './static/src/pages/index.js',
+ 'error' : './static/src/pages/error.js',
+ vendor: ['react', 'react-dom', 'whatwg-fetch'],
+ },
+ output: {
+ path: outputPath,
+ publicPath: '/static/output/dist/',
+ filename: 'js/[name].js',
+ },
+ module: {
+
+ rules: [
+ {
+ test: /\.(js|jsx)$/,
+ exclude: /node_modules/,
+ use: [
+ {
+ loader: 'babel-loader',
+ query: {
+ // presets: ['es2015', 'react'],
+ cacheDirectory: true
+ }
+ }
+ ]
+ },
+ {
+ test: /\.css$/,
+ use: ExtractTextPlugin.extract({
+ fallback: "style-loader",
+ use: ['css-loader']
+ }),
+ },
+ {
+ test: /\.scss$/,
+ use: ExtractTextPlugin.extract({
+ fallback: "style-loader",
+ use: ['css-loader', 'sass-loader']
+ })
+ },
+ {
+ test: /\.less$/,
+ use: ExtractTextPlugin.extract({
+ fallback: "style-loader",
+ use: ['css-loader', 'less-loader']
+ })
+ },
+ ]
+ },
+ resolve: {
+ extensions: ['.js', '.jsx'],
+ modules: [
+ sourcePath,
+ 'node_modules'
+ ]
+ },
+ plugins: [
+ new ExtractTextPlugin('css/[name].css'),
+ new webpack.optimize.CommonsChunkPlugin({
+ names: ['vendor'],
+ minChunks: Infinity,
+ filename: 'js/[name].js'
+ }),
+ ]
+};
\ No newline at end of file
diff --git a/demo/project/static/build/webpack.dev.config.js b/demo/project/static/build/webpack.dev.config.js
new file mode 100644
index 0000000..76869ed
--- /dev/null
+++ b/demo/project/static/build/webpack.dev.config.js
@@ -0,0 +1,19 @@
+var merge = require('webpack-merge')
+var webpack = require('webpack')
+var baseWebpackConfig = require('./webpack.base.config');
+
+module.exports = merge(baseWebpackConfig, {
+
+ devtool: 'source-map',
+ plugins: [
+
+ new webpack.DefinePlugin({
+ 'process.env': {
+ NODE_ENV: JSON.stringify('development')
+ }
+ }),
+ ]
+})
+
+
+
diff --git a/demo/project/static/build/webpack.prod.config.js b/demo/project/static/build/webpack.prod.config.js
new file mode 100644
index 0000000..b6e5575
--- /dev/null
+++ b/demo/project/static/build/webpack.prod.config.js
@@ -0,0 +1,22 @@
+var webpack = require('webpack');
+var merge = require('webpack-merge');
+var baseWebpackConfig = require('./webpack.base.config');
+
+module.exports = merge(baseWebpackConfig, {
+ // eval-source-map is faster for development
+
+ plugins: [
+
+ new webpack.DefinePlugin({
+ 'process.env': {
+ NODE_ENV: JSON.stringify('production')
+ }
+ }),
+ new webpack.optimize.UglifyJsPlugin({
+ minimize: true,
+ compress: {
+ warnings: false,
+ }
+ })
+ ]
+})
\ No newline at end of file
diff --git a/demo/project/static/output/asset/image/deepsea-logo.jpg b/demo/project/static/output/asset/image/deepsea-logo.jpg
new file mode 100644
index 0000000..b1dc798
Binary files /dev/null and b/demo/project/static/output/asset/image/deepsea-logo.jpg differ
diff --git a/demo/project/static/src/api/sign-in.js b/demo/project/static/src/api/sign-in.js
new file mode 100644
index 0000000..b97f15e
--- /dev/null
+++ b/demo/project/static/src/api/sign-in.js
@@ -0,0 +1,20 @@
+import Request from './../utils/request'
+
+const signInApi = async ( userInfo ) => {
+ let result = await Request.post({
+ url: '/api/user/signIn.json',
+ data: userInfo
+ })
+ return result
+}
+
+const signInForm = ( userInfo ) => {
+ userInfo.source = 'form';
+ Request.form({
+ url: '/api/user/signIn.json',
+ data: userInfo,
+ })
+}
+
+export { signInApi , signInForm }
+
diff --git a/demo/project/static/src/api/sign-up.js b/demo/project/static/src/api/sign-up.js
new file mode 100644
index 0000000..6188842
--- /dev/null
+++ b/demo/project/static/src/api/sign-up.js
@@ -0,0 +1,49 @@
+import Request from './../utils/request'
+import validator from 'validator'
+
+const signUpApi = async ( userInfo ) => {
+
+ let validateResult = validatorSignUp( userInfo )
+
+ if ( validateResult.success === false ) {
+ return validateResult
+ }
+
+ let result = Request.post({
+ url: '/api/user/signUp.json',
+ data: userInfo
+ })
+
+ return result;
+}
+
+const validatorSignUp = ( userInfo ) => {
+ let result = {
+ success: false,
+ message: '',
+ }
+
+ if ( /[a-z0-9\_\-]{6,16}/.test(userInfo.userName) === false ) {
+ result.message = '用户名格式为6-16位的小写字母,包括-、_'
+ return result
+ }
+ if ( !validator.isEmail( userInfo.email ) ) {
+ result.message = '请输入正确的邮箱地址'
+ return result
+ }
+ if ( !/[\w+]{6,16}/.test( userInfo.password ) ) {
+ result.message = '密码长度应该为6-16'
+ return result
+ }
+ if ( userInfo.password !== userInfo.confirmPassword ) {
+ result.message = '两次密码不一致'
+ return result
+ }
+
+ result.success = true
+
+ return result
+}
+
+
+export { signUpApi }
\ No newline at end of file
diff --git a/demo/project/static/src/apps/admin.jsx b/demo/project/static/src/apps/admin.jsx
new file mode 100644
index 0000000..c566767
--- /dev/null
+++ b/demo/project/static/src/apps/admin.jsx
@@ -0,0 +1,33 @@
+import React from 'react'
+import ReactDOM from 'react-dom'
+import { Layout, Menu, Breadcrumb } from 'antd'
+import FormGroup from './../components/form-group.jsx'
+import HeadeNav from './../components/header-nav.jsx'
+import FooterCommon from './../components/footer-common.jsx'
+
+import 'antd/lib/layout/style/css'
+
+const { Header, Content, Footer } = Layout
+
+class App extends React.Component {
+ render() {
+ return (
+
+
+
+
+ Admin
+ User
+
+
+
+
+
+
+
+ )
+ }
+}
+
+
+export default App
\ No newline at end of file
diff --git a/demo/project/static/src/apps/error.jsx b/demo/project/static/src/apps/error.jsx
new file mode 100644
index 0000000..ecc2276
--- /dev/null
+++ b/demo/project/static/src/apps/error.jsx
@@ -0,0 +1,28 @@
+import React from 'react'
+import ReactDOM from 'react-dom'
+import { Layout, Menu, Breadcrumb } from 'antd'
+import FooterCommon from './../components/footer-common.jsx'
+
+import 'antd/lib/layout/style/css'
+
+const { Header, Content, Footer } = Layout
+
+class App extends React.Component {
+ render() {
+ return (
+
+
+
+
+
+
+
+
+ )
+ }
+}
+
+
+export default App
\ No newline at end of file
diff --git a/demo/project/static/src/apps/index.jsx b/demo/project/static/src/apps/index.jsx
new file mode 100644
index 0000000..a271ac6
--- /dev/null
+++ b/demo/project/static/src/apps/index.jsx
@@ -0,0 +1,31 @@
+import React from 'react'
+import ReactDOM from 'react-dom'
+import { Layout, Menu, Breadcrumb } from 'antd'
+import HeadeNav from './../components/header-nav.jsx'
+import FooterCommon from './../components/footer-common.jsx'
+
+import 'antd/lib/layout/style/css'
+
+const { Header, Content, Footer } = Layout
+
+class App extends React.Component {
+ render() {
+ return (
+
+
+
+
+ Home
+
+
+
+
+
+ )
+ }
+}
+
+
+export default App
\ No newline at end of file
diff --git a/demo/project/static/src/apps/work.jsx b/demo/project/static/src/apps/work.jsx
new file mode 100644
index 0000000..c2e25b4
--- /dev/null
+++ b/demo/project/static/src/apps/work.jsx
@@ -0,0 +1,56 @@
+import React from 'react'
+import { Layout, Menu, Icon } from 'antd'
+const { Header, Sider, Content } = Layout
+
+class Work extends React.Component {
+ state = {
+ collapsed: false,
+ }
+ toggle = () => {
+ this.setState({
+ collapsed: !this.state.collapsed,
+ })
+ }
+ render() {
+ return (
+
+
+
+
+
+
+
+
+ Content
+
+
+
+ )
+ }
+}
+
+
+export default Work
\ No newline at end of file
diff --git a/demo/project/static/src/components/footer-common.jsx b/demo/project/static/src/components/footer-common.jsx
new file mode 100644
index 0000000..66078d4
--- /dev/null
+++ b/demo/project/static/src/components/footer-common.jsx
@@ -0,0 +1,17 @@
+import React from 'react'
+import ReactDOM from 'react-dom'
+import { Layout, Menu, Breadcrumb } from 'antd'
+const { Footer } = Layout
+
+class FooterCommon extends React.Component {
+ render() {
+ return (
+
+ )
+ }
+}
+
+
+export default FooterCommon
\ No newline at end of file
diff --git a/demo/project/static/src/components/form-group.jsx b/demo/project/static/src/components/form-group.jsx
new file mode 100644
index 0000000..632dfee
--- /dev/null
+++ b/demo/project/static/src/components/form-group.jsx
@@ -0,0 +1,25 @@
+import React from 'react'
+import { Tabs } from 'antd'
+import SignInForm from './../components/sign-in-form.jsx'
+import SignUpForm from './../components/sign-up-form.jsx'
+
+const TabPane = Tabs.TabPane
+
+class FormGroup extends React.Component {
+ render() {
+ return (
+
+
+
+
+
+
+
+
+
+
+ )
+ }
+}
+
+export default FormGroup
\ No newline at end of file
diff --git a/demo/project/static/src/components/header-nav.jsx b/demo/project/static/src/components/header-nav.jsx
new file mode 100644
index 0000000..a52d9ba
--- /dev/null
+++ b/demo/project/static/src/components/header-nav.jsx
@@ -0,0 +1,29 @@
+import React from 'react'
+import ReactDOM from 'react-dom'
+import { Layout, Menu, Breadcrumb } from 'antd'
+
+import 'antd/lib/layout/style/css'
+
+const { Header, Content, Footer } = Layout
+
+class HeaderNav extends React.Component {
+ render() {
+ return (
+
+ )
+ }
+}
+
+
+export default HeaderNav
\ No newline at end of file
diff --git a/demo/project/static/src/components/sign-in-form.jsx b/demo/project/static/src/components/sign-in-form.jsx
new file mode 100644
index 0000000..a8ca53a
--- /dev/null
+++ b/demo/project/static/src/components/sign-in-form.jsx
@@ -0,0 +1,77 @@
+import React from 'react'
+import { Form, Icon, Input, Button, Checkbox, message } from 'antd'
+import Request from './../utils/request'
+import { signInApi, signInForm } from './../api/sign-in'
+
+const FormItem = Form.Item;
+
+const SignInForm = Form.create()(React.createClass({
+
+ async handleSubmit(e) {
+ e.preventDefault()
+
+ let values = await this.getFormValues()
+ if ( values ) {
+ let result = await signInApi( values )
+ if ( result && result.success === true ) {
+ message.success( '登录成功!' )
+ signInForm( values )
+ } else if ( result && result.message ){
+ message.error( result.message )
+ }
+ } else {
+ message.error( '系统繁忙,稍后再试!' )
+ }
+ },
+
+
+ getFormValues() {
+ let that = this
+ return new Promise((resolve, reject) => {
+ that.props.form.validateFields((err, values) => {
+ if (!err) {
+ resolve( values )
+ } else {
+ reject( false )
+ }
+ })
+ })
+ },
+
+ render() {
+ const { getFieldDecorator } = this.props.form;
+ return (
+
+ );
+ },
+}))
+export default SignInForm
\ No newline at end of file
diff --git a/demo/project/static/src/components/sign-up-form.jsx b/demo/project/static/src/components/sign-up-form.jsx
new file mode 100644
index 0000000..6b04f45
--- /dev/null
+++ b/demo/project/static/src/components/sign-up-form.jsx
@@ -0,0 +1,166 @@
+import React from 'react'
+import { Form, Input, Tooltip, Icon, Cascader, Select, Row, Col, Checkbox, Button, message } from 'antd'
+import { signUpApi } from './../api/sign-up'
+
+const FormItem = Form.Item
+const Option = Select.Option
+
+
+const SignUpForm = Form.create()(React.createClass({
+
+ getInitialState() {
+ return {
+ passwordDirty: false,
+ }
+ },
+
+ async handleSubmit(e) {
+ e.preventDefault()
+ let values = await this.getFormValues()
+
+ if ( values ) {
+ let result = await signUpApi( values )
+ if ( result && result.success === true ) {
+ message.success( '注册成功!' )
+ window.location.href = '/admin?signUpSuccess=true'
+ } else if ( result && result.message ){
+ message.error( result.message )
+ }
+ } else {
+ message.error( '系统繁忙,稍后再试!' )
+ }
+
+ },
+
+ getFormValues() {
+ let that = this
+ return new Promise(( resolve, reject ) => {
+ that.props.form.validateFieldsAndScroll((err, values) => {
+ if (!err) {
+ resolve( values )
+ } else {
+ reject( false )
+ }
+ })
+ })
+ },
+
+ handlePasswordBlur(e) {
+ const value = e.target.value
+ this.setState({ passwordDirty: this.state.passwordDirty || !!value })
+ },
+
+ checkPassword(rule, value, callback) {
+ const form = this.props.form
+ if (value && value !== form.getFieldValue('password')) {
+ callback('两次密码输入不一致,请你检查!')
+ } else {
+ callback()
+ }
+ },
+
+ checkConfirm(rule, value, callback) {
+ const form = this.props.form
+ if (value && this.state.passwordDirty) {
+ form.validateFields(['confirm'], { force: true })
+ }
+ callback()
+ },
+
+ render() {
+ const { getFieldDecorator } = this.props.form
+ const formItemLayout = {
+ labelCol: { span: 6 },
+ wrapperCol: { span: 14 },
+ }
+ const tailFormItemLayout = {
+ wrapperCol: {
+ span: 14,
+ offset: 6,
+ },
+ }
+
+ return (
+
+ )
+ },
+}))
+
+
+export default SignUpForm
+
diff --git a/demo/project/static/src/pages/admin.js b/demo/project/static/src/pages/admin.js
new file mode 100644
index 0000000..a37dbb2
--- /dev/null
+++ b/demo/project/static/src/pages/admin.js
@@ -0,0 +1,7 @@
+import 'whatwg-fetch'
+import React from 'react'
+import ReactDOM from 'react-dom'
+import App from './../apps/admin.jsx'
+
+ReactDOM.render( ,
+ document.getElementById("app"))
diff --git a/demo/project/static/src/pages/error.js b/demo/project/static/src/pages/error.js
new file mode 100644
index 0000000..1a0ab4f
--- /dev/null
+++ b/demo/project/static/src/pages/error.js
@@ -0,0 +1,6 @@
+import React from 'react'
+import ReactDOM from 'react-dom'
+import App from './../apps/error.jsx'
+
+ReactDOM.render( ,
+ document.getElementById("app"))
\ No newline at end of file
diff --git a/demo/project/static/src/pages/index.js b/demo/project/static/src/pages/index.js
new file mode 100644
index 0000000..19d2420
--- /dev/null
+++ b/demo/project/static/src/pages/index.js
@@ -0,0 +1,6 @@
+import React from 'react'
+import ReactDOM from 'react-dom'
+import App from './../apps/index.jsx'
+
+ReactDOM.render( ,
+ document.getElementById("app"))
\ No newline at end of file
diff --git a/demo/project/static/src/pages/work.js b/demo/project/static/src/pages/work.js
new file mode 100644
index 0000000..0618f16
--- /dev/null
+++ b/demo/project/static/src/pages/work.js
@@ -0,0 +1,6 @@
+import React from 'react'
+import ReactDOM from 'react-dom'
+import App from './../apps/work.jsx'
+
+ReactDOM.render( ,
+ document.getElementById("app"))
\ No newline at end of file
diff --git a/demo/project/static/src/texts/common.js b/demo/project/static/src/texts/common.js
new file mode 100644
index 0000000..404dcf8
--- /dev/null
+++ b/demo/project/static/src/texts/common.js
@@ -0,0 +1,7 @@
+const common = {
+ BTN_SUBMIT_NAME: '确定',
+
+ NO_LOGIN: '未登录',
+}
+
+export default common
\ No newline at end of file
diff --git a/demo/project/static/src/texts/user-text.js b/demo/project/static/src/texts/user-text.js
new file mode 100644
index 0000000..128b6d4
--- /dev/null
+++ b/demo/project/static/src/texts/user-text.js
@@ -0,0 +1,23 @@
+const UserText = {
+
+ USER_INFO_LABEL_NAME: '用户名称',
+
+ USER_INFO_LABEL_EMAIL: '用户邮件',
+
+ USER_INFO_LABEL_PASSWORD: '密码',
+
+ USER_INFO_LABEL_CONFIRM_PASSWORD: '确认密码',
+
+ USER_INFO_PLACEHOLDER_NAME: '请输入用户名称',
+
+ USER_INFO_PLACEHOLDER_EMAIL: '请输入邮箱地址',
+
+ USER_INFO_PLACEHOLDER_PASSWORD: '请输入密码',
+
+ USER_INFO_PLACEHOLDER_CONFIRM_PASSWORD: '请再次输入密码确认',
+
+ USER_INFO_BTN_SUBMIT: '确认',
+
+}
+
+export default UserText
\ No newline at end of file
diff --git a/demo/project/static/src/utils/datetime.js b/demo/project/static/src/utils/datetime.js
new file mode 100644
index 0000000..ce99aa2
--- /dev/null
+++ b/demo/project/static/src/utils/datetime.js
@@ -0,0 +1,40 @@
+const monthEnum = [
+ '01','02','03','04','05','06',
+ '07','08','09','10','11','12',
+]
+
+const dayEnum = [
+ '01','02','03','04','05','06','07','08','09','10',
+ '11','12','13','14','15','16','17','18','19','20',
+ '21','22','23','04','25','26','27','28','29','30', '31',
+]
+
+const timeEnum = [
+ '00',
+ '01','02','03','04','05','06','07','08','09','10',
+ '11','12','13','14','15','16','17','18','19','20',
+ '21','22','23','04','25','26','27','28','29','30',
+ '31','32','33','34','35','36','37','38','39','40',
+ '41','42','43','44','45','46','47','48','49','50',
+ '51','52','53','54','55','56','57','58','59',
+]
+
+const datatime = {
+
+ parseStampToFormat( timestamp ) {
+ let _date = new Date(timestamp * 1);
+ let parsedDate = `${_date.getFullYear()}-${monthEnum[_date.getMonth()]}-${dayEnum[_date.getDate()-1]}`;
+ let parseTime = `${timeEnum[_date.getHours()]}:${timeEnum[_date.getMinutes()]}:${timeEnum[_date.getSeconds()]}`;
+ let parseDatetime = `${parsedDate} ${parseTime}`;
+ return parseDatetime;
+ },
+
+ getNowDatetime() {
+ let timestamp = new Date().getTime()
+ let nowDatetime = this.parseStampToFormat( timestamp )
+ return nowDatetime
+ }
+
+};
+
+export default datatime;
\ No newline at end of file
diff --git a/demo/project/static/src/utils/request.js b/demo/project/static/src/utils/request.js
new file mode 100644
index 0000000..a8b8a6e
--- /dev/null
+++ b/demo/project/static/src/utils/request.js
@@ -0,0 +1,132 @@
+import 'whatwg-fetch';
+
+function fetchEvent( options ) {
+ if ( !options ) {
+ return;
+ }
+ let _url = options.url || '';
+ let _type = options.type || 'GET';
+ let _data = options.data || {};
+ let _success;
+ let _error;
+ let fetchParams = {
+ credentials: 'include',
+ };
+ if ( _type === 'GET' ) {
+ let urlParams = [];
+ for ( let key in _data ) {
+ let _paramStr = '';
+ if ( typeof _data[key] === 'object' ) {
+ _paramStr = `${key}=${JSON.stringify(_data[key])}`;
+ } else {
+ _paramStr = `${key}=${_data[key]}`;
+ }
+ urlParams.push(_paramStr)
+ }
+
+ if ( _url.indexOf('?') >= 0 ) {
+ _url = `${_url}&${urlParams.join('&')}`
+ } else {
+ _url = `${_url}?${urlParams.join('&')}`
+ }
+ fetchParams = {
+ ...fetchParams,
+ ...{
+ headers: new Headers()
+ }
+ }
+ } else {
+ fetchParams = {
+ credentials: 'include',
+ method: _type,
+ headers: {'Content-Type': 'application/json'},
+ body: JSON.stringify(_data)
+ }
+ fetchParams = {
+ ...fetchParams,
+ ...{
+ method: _type,
+ headers: {'Content-Type': 'application/json'},
+ body: JSON.stringify(_data)
+ }
+ }
+ }
+
+ if ( typeof options.success === 'function' && typeof options.error === 'function' ) {
+ _success = options.success;
+ _error = options.error;
+ window.fetch(_url, fetchParams)
+ .then((response) => {
+ return response.json();
+ }).then( ( result ) => {
+ _success( result )
+ }).catch( ( err ) => {
+ _error( err )
+ })
+ } else {
+ // return window.fetch(_url, fetchParams)
+ // .then((response) => {
+ // return response.json();
+ // })
+
+ return new Promise(( resolve, reject ) => {
+ window.fetch(_url, fetchParams)
+ .then((response) => {
+ return response.json();
+ }).then( ( result ) => {
+ resolve( result )
+ }).catch( ( err ) => {
+ reject( err )
+ })
+ }).catch((err)=>{
+ console.log(err)
+ })
+ }
+}
+
+const request = {
+ get( options ) {
+ if ( typeof options !== 'object') {
+ return;
+ }
+ options.type = 'GET';
+ return fetchEvent( options );
+ },
+
+
+ post( options ) {
+ if ( typeof options !== 'object') {
+ return;
+ }
+ options.type = 'POST';
+ return fetchEvent( options );
+ },
+
+
+ form( options ) {
+ if ( typeof options !== 'object') {
+ return;
+ }
+ let _url = options.url || '';
+ let _data = options.data || {};
+ let _form = document.createElement('form');
+ _form.method = 'POST';
+ _form.action = _url;
+ for ( let key in _data ) {
+ let _input = document.createElement('input');
+ _input.type = 'hidden';
+ _input.name = key;
+ let _value = _data[key];
+ if ( typeof _value === 'object') {
+ _value = window.JSON.stringify(_value);
+ }
+ _input.value = _value;
+ _form.appendChild( _input );
+ }
+ _form.submit();
+
+ }
+
+};
+
+export default request;
diff --git a/demo/project/static/src/utils/tool.js b/demo/project/static/src/utils/tool.js
new file mode 100644
index 0000000..f347232
--- /dev/null
+++ b/demo/project/static/src/utils/tool.js
@@ -0,0 +1,51 @@
+
+let Global = window
+
+const tools = {
+
+ getUrlParam( name ) {
+ if (typeof name === 'undefined') {
+ return null
+ }
+
+ let paramReg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i")
+ let value = window.location.search.substr(1).match(paramReg)
+ if (value != null) {
+ return unescape(value[2])
+ }
+
+ return false
+ },
+
+ redirect( url ) {
+ Global.location.href = url
+ },
+
+ loadJs(url, callback) {
+ let _script = document.createElement('script')
+ _script.src = url
+ callback = callback || function(){}
+
+ if(navigator.userAgent.indexOf("MSIE")>0){
+ _script.onreadystatechange = function(){
+ if('loaded' === this.readyState || 'complete' === this.readyState){
+ callback()
+ this.onload = this.onreadystatechange = null
+ this.parentNode.removeChild(this)
+ }
+ }
+ }else{
+ _script.onload = function(){
+ callback()
+ this.onload = this.onreadystatechange = null
+ this.parentNode.removeChild(this)
+ }
+ }
+
+ document.getElementsByTagName('head')[0].appendChild(_script)
+ }
+
+}
+
+
+export default tools
diff --git a/demo/project/static/src/utils/upload.js b/demo/project/static/src/utils/upload.js
new file mode 100644
index 0000000..8ed42b6
--- /dev/null
+++ b/demo/project/static/src/utils/upload.js
@@ -0,0 +1,62 @@
+import UtilType from './type'
+
+function requestEvent( options ) {
+ try {
+ let formData = options.formData
+ let xhr = new XMLHttpRequest()
+ xhr.onreadystatechange = function() {
+
+ if ( xhr.readyState === 4 && xhr.status === 200 ) {
+ options.success(JSON.parse(xhr.responseText))
+ }
+ }
+
+ xhr.upload.onprogress = function(evt) {
+ let loaded = evt.loaded
+ let tot = evt.total
+ let per = Math.floor(100 * loaded / tot)
+ options.progress(per)
+ }
+ xhr.open('post', '/api/picture/upload.json')
+ xhr.send(formData)
+ } catch ( err ) {
+ options.fail(err)
+ }
+}
+
+function emitEvent ( options ){
+ let file
+ let formData = new FormData()
+ let input = document.createElement('input')
+ input.setAttribute('type', 'file')
+ input.setAttribute('name', 'files')
+
+ input.click()
+ input.onchange = function () {
+ file = input.files[0]
+ formData.append('files', file)
+
+ requestEvent({
+ formData,
+ success: options.success,
+ fail: options.fail,
+ progress: options.progress
+ })
+ }
+
+}
+
+function upload( options ) {
+ if ( !UtilType.isJSON( options ) ) {
+ console.log( 'upload options is null' )
+ return
+ }
+ let _options = {}
+ _options.success = UtilType.isFunction(options.success) ? options.success : function() {}
+ _options.fail = UtilType.isFunction(options.fail) ? options.fail : function() {}
+ _options.progress = UtilType.isFunction(options.progress) ? options.progress : function() {}
+
+ emitEvent(_options)
+}
+
+export default upload
\ No newline at end of file
diff --git a/demo/request/get.js b/demo/request/get.js
new file mode 100644
index 0000000..1f8d7e0
--- /dev/null
+++ b/demo/request/get.js
@@ -0,0 +1,26 @@
+const Koa = require('koa')
+const app = new Koa()
+
+app.use( async ( ctx ) => {
+ let url = ctx.url
+
+ // 从上下文的request对象中获取
+ let request = ctx.request
+ let req_query = request.query
+ let req_querystring = request.querystring
+
+ // 从上下文中直接获取
+ let ctx_query = ctx.query
+ let ctx_querystring = ctx.querystring
+
+ ctx.body = {
+ url,
+ req_query,
+ req_querystring,
+ ctx_query,
+ ctx_querystring
+ }
+})
+
+app.listen(3000)
+console.log('[demo] request get is starting at port 3000')
diff --git a/demo/request/package.json b/demo/request/package.json
new file mode 100644
index 0000000..483541d
--- /dev/null
+++ b/demo/request/package.json
@@ -0,0 +1,23 @@
+{
+ "name": "request-get",
+ "version": "1.0.0",
+ "description": "koa request get",
+ "main": "index.js",
+ "scripts": {
+ "get": "node --harmony get.js",
+ "post": "node --harmony post.js"
+ },
+ "keywords": [
+ "koajs"
+ ],
+ "author": "chenshenhai",
+ "license": "MIT",
+ "dependencies": {
+ "koa": "^2.0.0-alpha.8",
+ "koa-bodyparser": "^3.2.0"
+ },
+ "engines": {
+ "node": ">=7.0.0",
+ "npm": "~3.0.0"
+ }
+}
diff --git a/demo/request/post-middleware.js b/demo/request/post-middleware.js
new file mode 100644
index 0000000..fd528e3
--- /dev/null
+++ b/demo/request/post-middleware.js
@@ -0,0 +1,36 @@
+const Koa = require('koa')
+const app = new Koa()
+const bodyParser = require('koa-bodyparser')
+
+// 使用ctx.body解析中间件
+app.use(bodyParser())
+
+app.use( async ( ctx ) => {
+
+ if ( ctx.url === '/' && ctx.method === 'GET' ) {
+ // 当GET请求时候返回表单页面
+ let html = `
+ koa2 request post demo
+
+ `
+ ctx.body = html
+ } else if ( ctx.url === '/' && ctx.method === 'POST' ) {
+ // 当POST请求的时候,中间件koa-bodyparser解析POST表单里的数据,并显示出来
+ let postData = ctx.request.body
+ ctx.body = postData
+ } else {
+ // 其他请求显示404
+ ctx.body = '404!!! o(╯□╰)o
'
+ }
+})
+
+app.listen(3000)
+console.log('[demo] request post is starting at port 3000')
diff --git a/demo/request/post.js b/demo/request/post.js
new file mode 100644
index 0000000..0a7ea85
--- /dev/null
+++ b/demo/request/post.js
@@ -0,0 +1,62 @@
+const Koa = require('koa')
+const app = new Koa()
+
+app.use( async ( ctx ) => {
+
+ if ( ctx.url === '/' && ctx.method === 'GET' ) {
+ // 当GET请求时候返回表单页面
+ let html = `
+ koa2 request post demo
+
+ `
+ ctx.body = html
+ } else if ( ctx.url === '/' && ctx.method === 'POST' ) {
+ // 当POST请求的时候,解析POST表单里的数据,并显示出来
+ let postData = await parsePostData( ctx )
+ ctx.body = postData
+ } else {
+ // 其他请求显示404
+ ctx.body = '404!!! o(╯□╰)o
'
+ }
+})
+
+// 解析上下文里node原生请求的POST参数
+function parsePostData( ctx ) {
+ return new Promise((resolve, reject) => {
+ try {
+ let postdata = "";
+ ctx.req.addListener('data', (data) => {
+ postdata += data
+ })
+ ctx.req.addListener("end",function(){
+ let parseData = parseQueryStr( postdata )
+ resolve( parseData )
+ })
+ } catch ( err ) {
+ reject(err)
+ }
+ })
+}
+
+// 将POST请求参数字符串解析成JSON
+function parseQueryStr( queryStr ) {
+ let queryData = {}
+ let queryStrList = queryStr.split('&')
+ console.log( queryStrList )
+ for ( let [ index, queryStr ] of queryStrList.entries() ) {
+ let itemList = queryStr.split('=')
+ queryData[ itemList[0] ] = decodeURIComponent(itemList[1])
+ }
+ return queryData
+}
+
+app.listen(3000)
+console.log('[demo] request post is starting at port 3000')
diff --git a/demo/route-simple/index.js b/demo/route-simple/index.js
new file mode 100644
index 0000000..3e843ff
--- /dev/null
+++ b/demo/route-simple/index.js
@@ -0,0 +1,58 @@
+const Koa = require('koa')
+const fs = require('fs')
+const app = new Koa()
+
+/**
+ * 用Promise封装异步读取文件方法
+ * @param {string} page html文件名称
+ * @return {promise}
+ */
+function render( page ) {
+ return new Promise(( resolve, reject ) => {
+ let viewUrl = `./view/${page}`
+ fs.readFile(viewUrl, "binary", ( err, data ) => {
+ if ( err ) {
+ reject( err )
+ } else {
+ resolve( data )
+ }
+ })
+ })
+}
+
+
+/**
+ * 根据URL获取HTML内容
+ * @param {string} url koa2上下文的url,ctx.url
+ * @return {string} 获取HTML文件内容
+ */
+async function route( url ) {
+ let view = '404.html'
+ switch ( url ) {
+ case '/':
+ view = 'index.html'
+ break
+ case '/index':
+ view = 'index.html'
+ break
+ case '/todo':
+ view = 'todo.html'
+ break
+ case '/404':
+ view = '404.html'
+ break
+ default:
+ break
+ }
+ let html = await render( view )
+ return html
+}
+
+app.use( async ( ctx ) => {
+ let url = ctx.request.url
+ let html = await route( url )
+ ctx.body = html
+})
+
+app.listen(3000)
+console.log('[demo] route-simple is starting at port 3000')
diff --git a/demo/route-simple/package.json b/demo/route-simple/package.json
new file mode 100644
index 0000000..8bbddf6
--- /dev/null
+++ b/demo/route-simple/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "route-simple",
+ "version": "1.0.0",
+ "description": "koa route demo",
+ "main": "index.js",
+ "scripts": {
+ "start": "node --harmony index.js"
+ },
+ "keywords": [
+ "koajs"
+ ],
+ "author": "chenshenhai",
+ "license": "MIT",
+ "dependencies": {
+ "koa": "^2.0.0-alpha.8"
+ },
+ "engines": {
+ "node": ">=7.0.0",
+ "npm": "~3.0.0"
+ }
+}
diff --git a/demo/route-simple/view/404.html b/demo/route-simple/view/404.html
new file mode 100644
index 0000000..c0b8785
--- /dev/null
+++ b/demo/route-simple/view/404.html
@@ -0,0 +1,11 @@
+
+
+
+
+ 404
+
+
+ koa2 demo 404 page
+ this is a 404 page
+
+
diff --git a/demo/route-simple/view/index.html b/demo/route-simple/view/index.html
new file mode 100644
index 0000000..7b36bf7
--- /dev/null
+++ b/demo/route-simple/view/index.html
@@ -0,0 +1,18 @@
+
+
+
+
+ index
+
+
+ koa2 demo index page
+ this is a index page
+
+
+
diff --git a/demo/route-simple/view/todo.html b/demo/route-simple/view/todo.html
new file mode 100644
index 0000000..443a2b0
--- /dev/null
+++ b/demo/route-simple/view/todo.html
@@ -0,0 +1,11 @@
+
+
+
+
+ todo
+
+
+ koa2 demo todo page
+ this is a todo page
+
+
diff --git a/demo/route-use-middleware/index.js b/demo/route-use-middleware/index.js
new file mode 100644
index 0000000..ea83f40
--- /dev/null
+++ b/demo/route-use-middleware/index.js
@@ -0,0 +1,9 @@
+const Koa = require('koa')
+const fs = require('fs')
+const app = new Koa()
+const router = require('./routers/index')
+
+app.use(router.routes()).use(router.allowedMethods())
+
+app.listen(3000)
+console.log('[demo] route-use-middleware is starting at port 3000')
diff --git a/demo/route-use-middleware/package.json b/demo/route-use-middleware/package.json
new file mode 100644
index 0000000..f8eca55
--- /dev/null
+++ b/demo/route-use-middleware/package.json
@@ -0,0 +1,22 @@
+{
+ "name": "route-use-middleware",
+ "version": "1.0.0",
+ "description": "koa route demo",
+ "main": "index.js",
+ "scripts": {
+ "start": "node --harmony index.js"
+ },
+ "keywords": [
+ "koajs"
+ ],
+ "author": "chenshenhai",
+ "license": "MIT",
+ "dependencies": {
+ "koa": "^2.0.0-alpha.8",
+ "koa-router": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=7.0.0",
+ "npm": "~3.0.0"
+ }
+}
diff --git a/demo/route-use-middleware/quick.js b/demo/route-use-middleware/quick.js
new file mode 100644
index 0000000..be16b43
--- /dev/null
+++ b/demo/route-use-middleware/quick.js
@@ -0,0 +1,37 @@
+const Koa = require('koa')
+const fs = require('fs')
+const app = new Koa()
+
+const Router = require('koa-router')
+
+let home = new Router()
+
+// 子路由1
+home.get('/', async ( ctx )=>{
+ let html = `
+
+ `
+ ctx.body = html
+})
+
+// 子路由2
+let page = new Router()
+page.get('/404', async ( ctx )=>{
+ ctx.body = '404 page!'
+}).get('/helloworld', async ( ctx )=>{
+ ctx.body = 'helloworld page!'
+})
+
+// 装载所有子路由
+let router = new Router()
+router.use('/', home.routes(), home.allowedMethods())
+router.use('/page', page.routes(), page.allowedMethods())
+
+// 加载路由中间件
+app.use(router.routes()).use(router.allowedMethods())
+
+app.listen(3000)
+console.log('[demo] route-use-middleware is starting at port 3000')
diff --git a/demo/route-use-middleware/routers/api.js b/demo/route-use-middleware/routers/api.js
new file mode 100644
index 0000000..347c41d
--- /dev/null
+++ b/demo/route-use-middleware/routers/api.js
@@ -0,0 +1,17 @@
+const router = require('koa-router')()
+
+module.exports = router.get('/get/data.json', async ( ctx )=>{
+ ctx.body = {
+ success: true,
+ data: {
+ text: 'hello world!'
+ }
+ }
+}).get('/get/user.json', async ( ctx )=>{
+ ctx.body = {
+ success: true,
+ data: {
+ text: 'my name is koa.js!'
+ }
+ }
+})
diff --git a/demo/route-use-middleware/routers/home.js b/demo/route-use-middleware/routers/home.js
new file mode 100644
index 0000000..8928f09
--- /dev/null
+++ b/demo/route-use-middleware/routers/home.js
@@ -0,0 +1,13 @@
+const router = require('koa-router')()
+
+module.exports = router.get('/', async ( ctx )=>{
+ let html = `
+
+ `
+ ctx.body = html
+})
diff --git a/demo/route-use-middleware/routers/index.js b/demo/route-use-middleware/routers/index.js
new file mode 100644
index 0000000..3f67184
--- /dev/null
+++ b/demo/route-use-middleware/routers/index.js
@@ -0,0 +1,11 @@
+const router = require('koa-router')()
+
+const home = require('./home')
+const api = require('./api')
+const page = require('./page')
+
+router.use('/', home.routes(), home.allowedMethods())
+router.use('/api', api.routes(), api.allowedMethods())
+router.use('/page', page.routes(), page.allowedMethods())
+
+module.exports = router
diff --git a/demo/route-use-middleware/routers/page.js b/demo/route-use-middleware/routers/page.js
new file mode 100644
index 0000000..3c16268
--- /dev/null
+++ b/demo/route-use-middleware/routers/page.js
@@ -0,0 +1,7 @@
+const router = require('koa-router')()
+
+module.exports = router.get('/404', async ( ctx )=>{
+ ctx.body = '404 page!'
+}).get('/helloworld', async ( ctx )=>{
+ ctx.body = 'helloworld page!'
+})
diff --git a/demo/session/index.js b/demo/session/index.js
new file mode 100644
index 0000000..ced2df6
--- /dev/null
+++ b/demo/session/index.js
@@ -0,0 +1,55 @@
+const Koa = require('koa')
+const session = require('koa-session-minimal')
+const MysqlSession = require('koa-mysql-session')
+
+const app = new Koa()
+
+// 配置存储session信息的mysql
+let store = new MysqlSession({
+ user: 'root',
+ password: 'abc123',
+ database: 'koa_demo',
+ host: '127.0.0.1',
+})
+
+// 存放sessionId的cookie配置
+let cookie = {
+ maxAge: '', // cookie有效时长
+ expires: '', // cookie失效时间
+ path: '', // 写cookie所在的路径
+ domain: '', // 写cookie所在的域名
+ httpOnly: '', // 是否只用于http请求中获取
+ overwrite: '', // 是否允许重写
+ secure: '',
+ sameSite: '',
+ signed: '',
+
+}
+
+// 使用session中间件
+app.use(session({
+ key: 'SESSION_ID',
+ store: store,
+ cookie: cookie
+}))
+
+app.use( async ( ctx ) => {
+
+ // 设置session
+ if ( ctx.url === '/set' ) {
+ ctx.session = {
+ user_id: Math.random().toString(36).substr(2),
+ count: 0
+ }
+ ctx.body = ctx.session
+ } else if ( ctx.url === '/' ) {
+
+ // 读取session信息
+ ctx.session.count = ctx.session.count + 1
+ ctx.body = ctx.session
+ }
+
+})
+
+app.listen(3000)
+console.log('[demo] session is starting at port 3000')
diff --git a/demo/session/package.json b/demo/session/package.json
new file mode 100644
index 0000000..4eac82c
--- /dev/null
+++ b/demo/session/package.json
@@ -0,0 +1,23 @@
+{
+ "name": "session",
+ "version": "1.0.0",
+ "description": "koa start demo",
+ "main": "index.js",
+ "scripts": {
+ "start": "node --harmony index.js"
+ },
+ "keywords": [
+ "koajs"
+ ],
+ "author": "chenshenhai",
+ "license": "MIT",
+ "dependencies": {
+ "koa": "^2.0.0-alpha.8",
+ "koa-mysql-session": "^0.0.2",
+ "koa-session-minimal": "^3.0.3"
+ },
+ "engines": {
+ "node": ">=7.0.0",
+ "npm": "~3.0.0"
+ }
+}
diff --git a/demo/start-async/index.js b/demo/start-async/index.js
new file mode 100644
index 0000000..db0dab5
--- /dev/null
+++ b/demo/start-async/index.js
@@ -0,0 +1,11 @@
+const Koa = require('koa')
+const render = require('./util/render')
+const app = new Koa()
+
+app.use( async ( ctx ) => {
+ let html = await render('index.html')
+ ctx.body = html
+})
+
+app.listen(3000)
+console.log('[demo] start-async is starting at port 3000')
diff --git a/demo/start-async/package.json b/demo/start-async/package.json
new file mode 100644
index 0000000..258ca5a
--- /dev/null
+++ b/demo/start-async/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "start-quick",
+ "version": "1.0.0",
+ "description": "koa start demo",
+ "main": "index.js",
+ "scripts": {
+ "start": "node --harmony index.js"
+ },
+ "keywords": [
+ "koajs"
+ ],
+ "author": "chenshenhai",
+ "license": "MIT",
+ "dependencies": {
+ "koa": "^2.0.0-alpha.8"
+ },
+ "engines": {
+ "node": ">=7.0.0",
+ "npm": "~3.0.0"
+ }
+}
diff --git a/demo/start-async/util/render.js b/demo/start-async/util/render.js
new file mode 100644
index 0000000..f308c46
--- /dev/null
+++ b/demo/start-async/util/render.js
@@ -0,0 +1,21 @@
+const fs = require('fs')
+
+function render( page ) {
+
+ return new Promise(( resolve, reject ) => {
+
+ let viewUrl = `./view/${page}`
+ fs.readFile(viewUrl, "binary", ( err, data ) => {
+ if ( err ) {
+ reject( err )
+ } else {
+ resolve( data )
+ }
+ })
+ })
+
+}
+
+module.exports = render
+
+
diff --git a/demo/start-async/view/index.html b/demo/start-async/view/index.html
new file mode 100644
index 0000000..b44308f
--- /dev/null
+++ b/demo/start-async/view/index.html
@@ -0,0 +1,10 @@
+
+
+
+
+ index
+
+
+ koa2 async/await
+
+
\ No newline at end of file
diff --git a/demo/start-quick/index.js b/demo/start-quick/index.js
new file mode 100644
index 0000000..9f82448
--- /dev/null
+++ b/demo/start-quick/index.js
@@ -0,0 +1,9 @@
+const Koa = require('koa')
+const app = new Koa()
+
+app.use( async ( ctx ) => {
+ ctx.body = 'hello koa2'
+})
+
+app.listen(3000)
+console.log('[demo] start-quick is starting at port 3000')
diff --git a/demo/start-quick/package.json b/demo/start-quick/package.json
new file mode 100644
index 0000000..258ca5a
--- /dev/null
+++ b/demo/start-quick/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "start-quick",
+ "version": "1.0.0",
+ "description": "koa start demo",
+ "main": "index.js",
+ "scripts": {
+ "start": "node --harmony index.js"
+ },
+ "keywords": [
+ "koajs"
+ ],
+ "author": "chenshenhai",
+ "license": "MIT",
+ "dependencies": {
+ "koa": "^2.0.0-alpha.8"
+ },
+ "engines": {
+ "node": ">=7.0.0",
+ "npm": "~3.0.0"
+ }
+}
diff --git a/demo/static-server/index.js b/demo/static-server/index.js
new file mode 100644
index 0000000..3acdebb
--- /dev/null
+++ b/demo/static-server/index.js
@@ -0,0 +1,48 @@
+const Koa = require('koa')
+const path = require('path')
+const content = require('./util/content')
+const mimes = require('./util/mimes')
+
+const app = new Koa()
+
+// 静态资源目录对于相对入口文件index.js的路径
+const staticPath = './static'
+
+// 解析资源类型
+function parseMime( url ) {
+ let extName = path.extname( url )
+ extName = extName ? extName.slice(1) : 'unknown'
+ return mimes[ extName ]
+}
+
+app.use( async ( ctx ) => {
+ // 静态资源目录在本地的绝对路径
+ let fullStaticPath = path.join(__dirname, staticPath)
+
+ // 获取静态资源内容,有可能是文件内容,目录,或404
+ let _content = await content( ctx, fullStaticPath )
+
+ // 解析请求内容的类型
+ let _mime = parseMime( ctx.url )
+
+ // 如果有对应的文件类型,就配置上下文的类型
+ if ( _mime ) {
+ ctx.type = _mime
+ }
+
+ // 输出静态资源内容
+ if ( _mime && _mime.indexOf('image/') >= 0 ) {
+ // 如果是图片,则用node原生res,输出二进制数据
+ ctx.res.writeHead(200)
+ ctx.res.write(_content, 'binary')
+ ctx.res.end()
+ } else {
+ // 其他则输出文本
+ ctx.body = _content
+ }
+
+
+})
+
+app.listen(3000)
+console.log('[demo] static-server is starting at port 3000')
diff --git a/demo/static-server/package.json b/demo/static-server/package.json
new file mode 100644
index 0000000..f2b09d3
--- /dev/null
+++ b/demo/static-server/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "static-simple",
+ "version": "1.0.0",
+ "description": "koa start demo",
+ "main": "index.js",
+ "scripts": {
+ "start": "node --harmony index.js"
+ },
+ "keywords": [
+ "koajs"
+ ],
+ "author": "chenshenhai",
+ "license": "MIT",
+ "dependencies": {
+ "koa": "^2.0.0-alpha.8"
+ },
+ "engines": {
+ "node": ">=7.0.0",
+ "npm": "~3.0.0"
+ }
+}
diff --git a/demo/static-server/static/css/style.css b/demo/static-server/static/css/style.css
new file mode 100644
index 0000000..4f3e707
--- /dev/null
+++ b/demo/static-server/static/css/style.css
@@ -0,0 +1,5 @@
+.h1 {
+ padding: 20px;
+ color: #222;
+ background: #f0f0f0;
+}
diff --git a/demo/static-server/static/image/nodejs.jpg b/demo/static-server/static/image/nodejs.jpg
new file mode 100644
index 0000000..81820c4
Binary files /dev/null and b/demo/static-server/static/image/nodejs.jpg differ
diff --git a/demo/static-server/static/index.html b/demo/static-server/static/index.html
new file mode 100644
index 0000000..6f87289
--- /dev/null
+++ b/demo/static-server/static/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+ index
+
+
+
+ koa2 simple static server
+
+
+
+
diff --git a/demo/static-server/static/js/index.js b/demo/static-server/static/js/index.js
new file mode 100644
index 0000000..316c3f3
--- /dev/null
+++ b/demo/static-server/static/js/index.js
@@ -0,0 +1,4 @@
+(function( ){
+ alert('hello koa2 static server')
+ console.log('hello koa2 static server')
+})()
diff --git a/demo/static-server/util/content.js b/demo/static-server/util/content.js
new file mode 100644
index 0000000..095141a
--- /dev/null
+++ b/demo/static-server/util/content.js
@@ -0,0 +1,48 @@
+const path = require('path')
+const fs = require('fs')
+
+// 封装读取目录内容方法
+const dir = require('./dir')
+
+// 封装读取文件内容方法
+const file = require('./file')
+
+
+/**
+ * 获取静态资源内容
+ * @param {object} ctx koa上下文
+ * @param {string} 静态资源目录在本地的绝对路径
+ * @return {string} 请求获取到的本地内容
+ */
+async function content( ctx, fullStaticPath ) {
+
+ // 封装请求资源的完绝对径
+ let reqPath = path.join(fullStaticPath, ctx.url)
+
+ // 判断请求路径是否为存在目录或者文件
+ let exist = fs.existsSync( reqPath )
+
+ // 返回请求内容, 默认为空
+ let content = ''
+
+ if( !exist ) {
+ //如果请求路径不存在,返回404
+ content = '404 Not Found! o(╯□╰)o!'
+ } else {
+ //判断访问地址是文件夹还是文件
+ let stat = fs.statSync( reqPath )
+
+ if( stat.isDirectory() ) {
+ //如果为目录,则渲读取目录内容
+ content = dir( ctx.url, reqPath )
+
+ } else {
+ // 如果请求为文件,则读取文件内容
+ content = file( reqPath )
+ }
+ }
+
+ return content
+}
+
+module.exports = content
diff --git a/demo/static-server/util/dir.js b/demo/static-server/util/dir.js
new file mode 100644
index 0000000..806c82b
--- /dev/null
+++ b/demo/static-server/util/dir.js
@@ -0,0 +1,28 @@
+const url = require('url')
+const fs = require('fs')
+const path = require('path')
+
+// 遍历读取目录内容方法
+const walk = require('./walk')
+
+/**
+ * 封装目录内容
+ * @param {string} url 当前请求的上下文中的url,即ctx.url
+ * @param {string} reqPath 请求静态资源的完整本地路径
+ * @return {string} 返回目录内容,封装成HTML
+ */
+function dir ( url, reqPath ) {
+
+ // 遍历读取当前目录下的文件、子目录
+ let contentList = walk( reqPath )
+
+ let html = ``
+ for ( let [ index, item ] of contentList.entries() ) {
+ html = `${html}- ${item}`
+ }
+ html = `${html}
`
+
+ return html
+}
+
+module.exports = dir
\ No newline at end of file
diff --git a/demo/static-server/util/file.js b/demo/static-server/util/file.js
new file mode 100644
index 0000000..45b561d
--- /dev/null
+++ b/demo/static-server/util/file.js
@@ -0,0 +1,14 @@
+const fs = require('fs')
+
+/**
+ * 读取文件方法
+ * @param {string} 文件本地的绝对路径
+ * @return {string|binary}
+ */
+function file ( filePath ) {
+
+ let content = fs.readFileSync(filePath, 'binary' )
+ return content
+}
+
+module.exports = file
diff --git a/demo/static-server/util/mimes.js b/demo/static-server/util/mimes.js
new file mode 100644
index 0000000..ffe94e0
--- /dev/null
+++ b/demo/static-server/util/mimes.js
@@ -0,0 +1,23 @@
+let mimes = {
+ 'css': 'text/css',
+ 'less': 'text/css',
+ 'gif': 'image/gif',
+ 'html': 'text/html',
+ 'ico': 'image/x-icon',
+ 'jpeg': 'image/jpeg',
+ 'jpg': 'image/jpeg',
+ 'js': 'text/javascript',
+ 'json': 'application/json',
+ 'pdf': 'application/pdf',
+ 'png': 'image/png',
+ 'svg': 'image/svg+xml',
+ 'swf': 'application/x-shockwave-flash',
+ 'tiff': 'image/tiff',
+ 'txt': 'text/plain',
+ 'wav': 'audio/x-wav',
+ 'wma': 'audio/x-ms-wma',
+ 'wmv': 'video/x-ms-wmv',
+ 'xml': 'text/xml'
+}
+
+module.exports = mimes
diff --git a/demo/static-server/util/walk.js b/demo/static-server/util/walk.js
new file mode 100644
index 0000000..dc306ff
--- /dev/null
+++ b/demo/static-server/util/walk.js
@@ -0,0 +1,32 @@
+const fs = require('fs')
+const mimes = require('./mimes')
+
+/**
+ * 遍历读取目录内容(子目录,文件名)
+ * @param {string} reqPath 请求资源的绝对路径
+ * @return {array} 目录内容列表
+ */
+function walk( reqPath ){
+
+ let files = fs.readdirSync( reqPath );
+
+ let dirList = [], fileList = [];
+ for( let i=0, len=files.length; i 1 ) ? itemArr[ itemArr.length - 1 ] : "undefined";
+
+ if( typeof mimes[ itemMime ] === "undefined" ) {
+ dirList.push( files[i] );
+ } else {
+ fileList.push( files[i] );
+ }
+ }
+
+
+ let result = dirList.concat( fileList );
+
+ return result;
+};
+
+module.exports = walk;
diff --git a/demo/static-use-middleware/index.js b/demo/static-use-middleware/index.js
new file mode 100644
index 0000000..86e9617
--- /dev/null
+++ b/demo/static-use-middleware/index.js
@@ -0,0 +1,23 @@
+const Koa = require('koa')
+const path = require('path')
+const convert = require('koa-convert')
+const static = require('koa-static')
+
+const app = new Koa()
+
+// 静态资源目录对于相对入口文件index.js的路径
+const staticPath = './static'
+
+// 由于koa-static目前不支持koa2
+// 所以只能用koa-convert封装一下
+app.use(convert(static(
+ path.join( __dirname, staticPath)
+)))
+
+
+app.use( async ( ctx ) => {
+ ctx.body = 'hello world'
+})
+
+app.listen(3000)
+console.log('[demo] static-use-middleware is starting at port 3000')
diff --git a/demo/static-use-middleware/package.json b/demo/static-use-middleware/package.json
new file mode 100644
index 0000000..05a3b6c
--- /dev/null
+++ b/demo/static-use-middleware/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "static-use-middleware",
+ "version": "1.0.0",
+ "description": "koa start demo",
+ "main": "index.js",
+ "scripts": {
+ "start": "node --harmony index.js"
+ },
+ "keywords": [
+ "koajs"
+ ],
+ "author": "chenshenhai",
+ "license": "MIT",
+ "dependencies": {
+ "koa": "^2.0.0-alpha.8"
+ },
+ "engines": {
+ "node": ">=7.0.0",
+ "npm": "~3.0.0"
+ }
+}
diff --git a/demo/static-use-middleware/static/css/style.css b/demo/static-use-middleware/static/css/style.css
new file mode 100644
index 0000000..4f3e707
--- /dev/null
+++ b/demo/static-use-middleware/static/css/style.css
@@ -0,0 +1,5 @@
+.h1 {
+ padding: 20px;
+ color: #222;
+ background: #f0f0f0;
+}
diff --git a/demo/static-use-middleware/static/image/nodejs.jpg b/demo/static-use-middleware/static/image/nodejs.jpg
new file mode 100644
index 0000000..81820c4
Binary files /dev/null and b/demo/static-use-middleware/static/image/nodejs.jpg differ
diff --git a/demo/static-use-middleware/static/index.html b/demo/static-use-middleware/static/index.html
new file mode 100644
index 0000000..6f87289
--- /dev/null
+++ b/demo/static-use-middleware/static/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+ index
+
+
+
+ koa2 simple static server
+
+
+
+
diff --git a/demo/static-use-middleware/static/js/index.js b/demo/static-use-middleware/static/js/index.js
new file mode 100644
index 0000000..316c3f3
--- /dev/null
+++ b/demo/static-use-middleware/static/js/index.js
@@ -0,0 +1,4 @@
+(function( ){
+ alert('hello koa2 static server')
+ console.log('hello koa2 static server')
+})()
diff --git a/demo/upload/index.js b/demo/upload/index.js
new file mode 100644
index 0000000..b0c6060
--- /dev/null
+++ b/demo/upload/index.js
@@ -0,0 +1,43 @@
+const Koa = require('koa')
+const path = require('path')
+const app = new Koa()
+// const bodyParser = require('koa-bodyparser')
+
+const { uploadFile } = require('./util/upload')
+
+// app.use(bodyParser())
+
+app.use( async ( ctx ) => {
+
+ if ( ctx.url === '/' && ctx.method === 'GET' ) {
+ // 当GET请求时候返回表单页面
+ let html = `
+ koa2 upload demo
+
+ `
+ ctx.body = html
+
+ } else if ( ctx.url === '/upload.json' && ctx.method === 'POST' ) {
+ // 上传文件请求处理
+ let result = { success: false }
+ let serverFilePath = path.join( __dirname, 'upload-files' )
+
+ // 上传文件事件
+ result = await uploadFile( ctx, {
+ fileType: 'album',
+ path: serverFilePath
+ })
+
+ ctx.body = result
+ } else {
+ // 其他请求显示404
+ ctx.body = '404!!! o(╯□╰)o
'
+ }
+})
+
+app.listen(3000)
+console.log('[demo] upload-simple is starting at port 3000')
diff --git a/demo/upload/package.json b/demo/upload/package.json
new file mode 100644
index 0000000..6d56887
--- /dev/null
+++ b/demo/upload/package.json
@@ -0,0 +1,22 @@
+{
+ "name": "request-get",
+ "version": "1.0.0",
+ "description": "koa request get",
+ "main": "index.js",
+ "scripts": {
+ "start": "node --harmony index.js"
+ },
+ "keywords": [
+ "koajs"
+ ],
+ "author": "chenshenhai",
+ "license": "MIT",
+ "dependencies": {
+ "busboy": "^0.2.14",
+ "koa": "^2.0.0-alpha.8"
+ },
+ "engines": {
+ "node": ">=7.0.0",
+ "npm": "~3.0.0"
+ }
+}
diff --git a/demo/upload/util/upload.js b/demo/upload/util/upload.js
new file mode 100644
index 0000000..5d43e21
--- /dev/null
+++ b/demo/upload/util/upload.js
@@ -0,0 +1,94 @@
+const inspect = require('util').inspect
+const path = require('path')
+const os = require('os')
+const fs = require('fs')
+const Busboy = require('busboy')
+
+/**
+ * 同步创建文件目录
+ * @param {string} dirname 目录绝对地址
+ * @return {boolean} 创建目录结果
+ */
+function mkdirsSync( dirname ) {
+ if (fs.existsSync( dirname )) {
+ return true
+ } else {
+ if (mkdirsSync( path.dirname(dirname)) ) {
+ fs.mkdirSync( dirname )
+ return true
+ }
+ }
+}
+
+/**
+ * 获取上传文件的后缀名
+ * @param {string} fileName 获取上传文件的后缀名
+ * @return {string} 文件后缀名
+ */
+function getSuffixName( fileName ) {
+ let nameList = fileName.split('.')
+ return nameList[nameList.length - 1]
+}
+
+/**
+ * 上传文件
+ * @param {object} ctx koa上下文
+ * @param {object} options 文件上传参数 fileType文件类型, path文件存放路径
+ * @return {promise}
+ */
+function uploadFile( ctx, options) {
+ let req = ctx.req
+ let res = ctx.res
+ let busboy = new Busboy({headers: req.headers})
+
+ // 获取类型
+ let fileType = options.fileType || 'common'
+ let filePath = path.join( options.path, fileType)
+ let mkdirResult = mkdirsSync( filePath )
+
+ return new Promise((resolve, reject) => {
+ console.log('文件上传中...')
+ let result = {
+ success: false
+ }
+
+ // 解析请求文件事件
+ busboy.on('file', function(fieldname, file, filename, encoding, mimetype) {
+ let fileName = Math.random().toString(16).substr(2) + '.' + getSuffixName(filename)
+ let _uploadFilePath = path.join( filePath, fileName )
+ let saveTo = path.join(_uploadFilePath)
+
+ // 文件保存到制定路径
+ file.pipe(fs.createWriteStream(saveTo))
+
+ // 文件写入事件结束
+ file.on('end', function() {
+ result.success = true
+ result.message = '文件上传成功'
+
+ console.log('文件上传成功!')
+ resolve(result)
+ })
+ })
+
+ // 解析结束事件
+ busboy.on('finish', function( ) {
+ console.log('文件上结束')
+ resolve(result)
+ })
+
+ // 解析错误事件
+ busboy.on('error', function(err) {
+ console.log('文件上出错')
+ reject(result)
+ })
+
+ req.pipe(busboy)
+ })
+
+}
+
+
+module.exports = {
+ uploadFile
+}
diff --git a/note/cookie/info.md b/note/cookie/info.md
new file mode 100644
index 0000000..ed99064
--- /dev/null
+++ b/note/cookie/info.md
@@ -0,0 +1,59 @@
+# koa2使用cookie
+
+## 使用方法
+
+koa提供了从上下文直接读取、写入cookie的方法
+- ctx.cookies.get(name, [options]) 读取上下文请求中的cookie
+- ctx.cookies.set(name, value, [options]) 在上下文中写入cookie
+
+koa2 中操作的cookies是使用了npm的cookies模块,源码在[https://github.com/pillarjs/cookies](https://github.com/pillarjs/cookies),所以在读写cookie的使用参数与该模块的使用一致。
+
+
+## 例子代码
+``` js
+const Koa = require('koa')
+const app = new Koa()
+
+app.use( async ( ctx ) => {
+
+ if ( ctx.url === '/index' ) {
+ ctx.cookies.set(
+ 'cid',
+ 'hello world',
+ {
+ domain: 'localhost', // 写cookie所在的域名
+ path: '/index', // 写cookie所在的路径
+ maxAge: 10 * 60 * 1000, // cookie有效时长
+ expires: new Date('2017-02-15'), // cookie失效时间
+ httpOnly: false, // 是否只用于http请求中获取
+ overwrite: false // 是否允许重写
+ }
+ )
+ ctx.body = 'cookie is ok'
+ } else {
+ ctx.body = 'hello world'
+ }
+
+})
+
+app.listen(3000)
+console.log('[demo] cookie is starting at port 3000')
+
+```
+
+## 运行例子
+
+### 执行脚本
+```sh
+node --harmony index.js
+```
+
+### 运行结果
+
+#### 访问[http://localhost:3000/index](http://localhost:3000/index)
+- 可以在控制台的cookie列表中中看到写在页面上的cookie
+- 在控制台的console中使用document.cookie可以打印出在页面的所有cookie(需要是httpOnly设置false才能显示)
+
+![cookie-result-01](./../images/cookie-result-01.png)
+
+
diff --git a/note/debug/info.md b/note/debug/info.md
new file mode 100644
index 0000000..7a2f24a
--- /dev/null
+++ b/note/debug/info.md
@@ -0,0 +1,2 @@
+# 开发debug
+> 待续。。。
\ No newline at end of file
diff --git a/note/images/async.png b/note/images/async.png
new file mode 100644
index 0000000..83b1bee
Binary files /dev/null and b/note/images/async.png differ
diff --git a/note/images/cookie-result-01.png b/note/images/cookie-result-01.png
new file mode 100644
index 0000000..89d957e
Binary files /dev/null and b/note/images/cookie-result-01.png differ
diff --git a/note/images/mysql-init-result-01.png b/note/images/mysql-init-result-01.png
new file mode 100644
index 0000000..8cd2b2f
Binary files /dev/null and b/note/images/mysql-init-result-01.png differ
diff --git a/note/images/mysql-init-result-02.png b/note/images/mysql-init-result-02.png
new file mode 100644
index 0000000..c81f024
Binary files /dev/null and b/note/images/mysql-init-result-02.png differ
diff --git a/note/images/project-result-00.png b/note/images/project-result-00.png
new file mode 100644
index 0000000..af54bf7
Binary files /dev/null and b/note/images/project-result-00.png differ
diff --git a/note/images/project-result-01.png b/note/images/project-result-01.png
new file mode 100644
index 0000000..0234cd4
Binary files /dev/null and b/note/images/project-result-01.png differ
diff --git a/note/images/project-result-02.png b/note/images/project-result-02.png
new file mode 100644
index 0000000..0855e11
Binary files /dev/null and b/note/images/project-result-02.png differ
diff --git a/note/images/project-result-03.png b/note/images/project-result-03.png
new file mode 100644
index 0000000..1608947
Binary files /dev/null and b/note/images/project-result-03.png differ
diff --git a/note/images/request-get.png b/note/images/request-get.png
new file mode 100644
index 0000000..fa72d7b
Binary files /dev/null and b/note/images/request-get.png differ
diff --git a/note/images/request-post-form.png b/note/images/request-post-form.png
new file mode 100644
index 0000000..8c10cec
Binary files /dev/null and b/note/images/request-post-form.png differ
diff --git a/note/images/request-post-result.png b/note/images/request-post-result.png
new file mode 100644
index 0000000..3b63f7e
Binary files /dev/null and b/note/images/request-post-result.png differ
diff --git a/note/images/route-result-01.png b/note/images/route-result-01.png
new file mode 100644
index 0000000..ef11a8d
Binary files /dev/null and b/note/images/route-result-01.png differ
diff --git a/note/images/session-result-01.png b/note/images/session-result-01.png
new file mode 100644
index 0000000..b31971d
Binary files /dev/null and b/note/images/session-result-01.png differ
diff --git a/note/images/session-result-02.png b/note/images/session-result-02.png
new file mode 100644
index 0000000..3d7814d
Binary files /dev/null and b/note/images/session-result-02.png differ
diff --git a/note/images/session-result-03.png b/note/images/session-result-03.png
new file mode 100644
index 0000000..c395d28
Binary files /dev/null and b/note/images/session-result-03.png differ
diff --git a/note/images/start-result-01.png b/note/images/start-result-01.png
new file mode 100644
index 0000000..9d53203
Binary files /dev/null and b/note/images/start-result-01.png differ
diff --git a/note/images/static-server-result-01.png b/note/images/static-server-result-01.png
new file mode 100644
index 0000000..a50805f
Binary files /dev/null and b/note/images/static-server-result-01.png differ
diff --git a/note/images/static-server-result-02.png b/note/images/static-server-result-02.png
new file mode 100644
index 0000000..3143917
Binary files /dev/null and b/note/images/static-server-result-02.png differ
diff --git a/note/images/static-server-result-03.png b/note/images/static-server-result-03.png
new file mode 100644
index 0000000..d409c90
Binary files /dev/null and b/note/images/static-server-result-03.png differ
diff --git a/note/images/upload-simple-result-01.png b/note/images/upload-simple-result-01.png
new file mode 100644
index 0000000..06e5c2d
Binary files /dev/null and b/note/images/upload-simple-result-01.png differ
diff --git a/note/images/upload-simple-result-02.png b/note/images/upload-simple-result-02.png
new file mode 100644
index 0000000..bd0285a
Binary files /dev/null and b/note/images/upload-simple-result-02.png differ
diff --git a/note/images/upload-simple-result-03.png b/note/images/upload-simple-result-03.png
new file mode 100644
index 0000000..8a1da34
Binary files /dev/null and b/note/images/upload-simple-result-03.png differ
diff --git a/note/images/upload-simple-result-04.png b/note/images/upload-simple-result-04.png
new file mode 100644
index 0000000..fcdc4ed
Binary files /dev/null and b/note/images/upload-simple-result-04.png differ
diff --git a/note/mysql/async.md b/note/mysql/async.md
new file mode 100644
index 0000000..9e3dac1
--- /dev/null
+++ b/note/mysql/async.md
@@ -0,0 +1,60 @@
+# async/await封装使用mysql
+
+## 前言
+由于mysql模块的操作都是异步操作,每次操作的结果都是在回调函数中执行,现在有了async/await,就可以用同步的写法去操作数据库
+
+### Promise封装mysql模块
+
+#### Promise封装 ./async-db
+```js
+const mysql = require('mysql')
+const pool = mysql.createPool({
+ host : '127.0.0.1',
+ user : 'root',
+ password : '123456',
+ database : 'my_database'
+})
+
+let query = function( sql, values ) {
+ return new Promise(( resolve, reject ) => {
+ pool.getConnection(function(err, connection) {
+ if (err) {
+ resolve( err )
+ } else {
+ connection.query(sql, values, ( err, rows) => {
+
+ if ( err ) {
+ reject( err )
+ } else {
+ resolve( rows )
+ }
+ connection.release()
+ })
+ }
+ })
+ })
+}
+
+module.exports = { query }
+```
+
+#### async/await使用
+```js
+const { query } = require('./async-db')
+async function selectAllData( ) {
+ let sql = 'SELECT * FROM my_table'
+ let dataList = await query( sql )
+ return dataList
+}
+
+async function getData() {
+ let dataList = await selectAllData()
+ console.log( dataList )
+}
+
+getData()
+```
+
+
+
+
diff --git a/note/mysql/info.md b/note/mysql/info.md
new file mode 100644
index 0000000..8765f9e
--- /dev/null
+++ b/note/mysql/info.md
@@ -0,0 +1,69 @@
+# mysql模块
+
+## 快速开始
+
+### 安装
+
+```
+npm install --save mysql
+```
+
+### 模块介绍
+mysql模块是node操作MySQL的引擎,可以在node.js环境下对MySQL数据库进行建表,增、删、改、查等操作。
+
+### 开始使用
+
+#### 创建数据库会话
+```js
+const mysql = require('mysql')
+const connection = mysql.createConnection({
+ host : '127.0.0.1', // 数据库地址
+ user : 'root', // 数据库用户
+ password : '123456' // 数据库密码
+ database : 'my_database' // 选中数据库
+})
+
+// 执行sql脚本对数据库进行读写
+connection.query('SELECT * FROM my_table', (error, results, fields) => {
+ if (error) throw error
+ // connected!
+
+ // 结束会话
+ connection.release()
+});
+```
+
+> 注意:一个事件就有一个从开始到结束的过程,数据库会话操作执行完后,就需要关闭掉,以免占用连接资源。
+
+#### 创建数据连接池
+一般情况下操作数据库是很复杂的读写过程,不只是一个会话,如果直接用会话操作,就需要每次会话都要配置连接参数。所以这时候就需要连接池管理会话。
+
+```js
+const mysql = require('mysql')
+
+// 创建数据池
+const pool = mysql.createPool({
+ host : '127.0.0.1', // 数据库地址
+ user : 'root', // 数据库用户
+ password : '123456' // 数据库密码
+ database : 'my_database' // 选中数据库
+})
+
+// 在数据池中进行会话操作
+pool.getConnection(function(err, connection) {
+
+ connection.query('SELECT * FROM my_table', (error, results, fields) => {
+
+ // 结束会话
+ connection.release();
+
+ // 如果有错误就抛出
+ if (error) throw error;
+ })
+})
+```
+
+## 更多模块信息
+更多详细API可以访问npm官方文档 [https://www.npmjs.com/package/mysql](https://www.npmjs.com/package/mysql)
+
+
diff --git a/note/mysql/init.md b/note/mysql/init.md
new file mode 100644
index 0000000..086fed3
--- /dev/null
+++ b/note/mysql/init.md
@@ -0,0 +1,266 @@
+# 建表初始化
+
+## 前言
+通常初始化数据库要建立很多表,特别在项目开发的时候表的格式可能会有些变动,这时候就需要封装对数据库建表初始化的方法,保留项目的sql脚本文件,然后每次需要重新建表,则执行建表初始化程序就行
+
+## 快速开始
+
+### 源码目录
+
+``` sh
+├── index.js # 程序入口文件
+├── node_modules/
+├── package.json
+├── sql # sql脚本文件目录
+│ ├── data.sql
+│ └── user.sql
+└── util # 工具代码
+ ├── db.js # 封装的mysql模块方法
+ ├── get-sql-content-map.js # 获取sql脚本文件内容
+ ├── get-sql-map.js # 获取所有sql脚本文件
+ └── walk-file.js # 遍历sql脚本文件
+```
+
+### 具体流程
+```sh
+ +---------------------------------------------------+
+ | |
+ | +-----------+ +-----------+ +-----------+ |
+ | | | | | | | |
+ | | | | | | | |
+ | | | | | | | |
+ | | | | | | | |
++----------+ 遍历sql +---+ 解析所有sql +---+ 执行sql +------------>
+ | | 目录下的 | | 文件脚本 | | 脚本 | |
++----------+ sql文件 +---+ 内容 +---+ +------------>
+ | | | | | | | |
+ | | | | | | | |
+ | | | | | | | |
+ | | | | | | | |
+ | +-----------+ +-----------+ +-----------+ |
+ | |
+ +---------------------------------------------------+
+
+```
+
+## 源码详解
+
+### 数据库操作文件 ./util/db.js
+```js
+const mysql = require('mysql')
+
+const pool = mysql.createPool({
+ host : '127.0.0.1',
+ user : 'root',
+ password : 'abc123',
+ database : 'koa_demo'
+})
+
+let query = function( sql, values ) {
+
+ return new Promise(( resolve, reject ) => {
+ pool.getConnection(function(err, connection) {
+ if (err) {
+ resolve( err )
+ } else {
+ connection.query(sql, values, ( err, rows) => {
+
+ if ( err ) {
+ reject( err )
+ } else {
+ resolve( rows )
+ }
+ connection.release()
+ })
+ }
+ })
+ })
+
+}
+
+module.exports = {
+ query
+}
+```
+
+### 获取所有sql脚本内容 ./util/get-sql-content-map.js
+```js
+const fs = require('fs')
+const getSqlMap = require('./get-sql-map')
+
+let sqlContentMap = {}
+
+/**
+ * 读取sql文件内容
+ * @param {string} fileName 文件名称
+ * @param {string} path 文件所在的路径
+ * @return {string} 脚本文件内容
+ */
+function getSqlContent( fileName, path ) {
+ let content = fs.readFileSync( path, 'binary' )
+ sqlContentMap[ fileName ] = content
+}
+
+/**
+ * 封装所有sql文件脚本内容
+ * @return {object}
+ */
+function getSqlContentMap () {
+ let sqlMap = getSqlMap()
+ for( let key in sqlMap ) {
+ getSqlContent( key, sqlMap[key] )
+ }
+
+ return sqlContentMap
+}
+
+module.exports = getSqlContentMap
+```
+
+### 获取sql目录详情 ./util/get-sql-map.js
+```js
+const fs = require('fs')
+const walkFile = require('./walk-file')
+
+/**
+ * 获取sql目录下的文件目录数据
+ * @return {object}
+ */
+function getSqlMap () {
+ let basePath = __dirname
+ basePath = basePath.replace(/\\/g, '\/')
+
+ let pathArr = basePath.split('\/')
+ pathArr = pathArr.splice( 0, pathArr.length - 1 )
+ basePath = pathArr.join('/') + '/sql/'
+
+ let fileList = walkFile( basePath, 'sql' )
+ return fileList
+}
+
+module.exports = getSqlMap
+```
+
+### 遍历目录操作 ./util/walk-file.js
+```js
+const fs = require('fs')
+
+/**
+ * 遍历目录下的文件目录
+ * @param {string} pathResolve 需进行遍历的目录路径
+ * @param {string} mime 遍历文件的后缀名
+ * @return {object} 返回遍历后的目录结果
+ */
+const walkFile = function( pathResolve , mime ){
+
+ let files = fs.readdirSync( pathResolve )
+
+ let fileList = {}
+
+ for( let [ i, item] of files.entries() ) {
+ let itemArr = item.split('\.')
+
+ let itemMime = ( itemArr.length > 1 ) ? itemArr[ itemArr.length - 1 ] : 'undefined'
+ let keyName = item + ''
+ if( mime === itemMime ) {
+ fileList[ item ] = pathResolve + item
+ }
+ }
+
+ return fileList
+}
+
+module.exports = walkFile
+```
+
+### 入口文件 ./index.js
+```js
+
+const fs = require('fs');
+const getSqlContentMap = require('./util/get-sql-content-map');
+const { query } = require('./util/db');
+
+
+// 打印脚本执行日志
+const eventLog = function( err , sqlFile, index ) {
+ if( err ) {
+ console.log(`[ERROR] sql脚本文件: ${sqlFile} 第${index + 1}条脚本 执行失败 o(╯□╰)o !`)
+ } else {
+ console.log(`[SUCCESS] sql脚本文件: ${sqlFile} 第${index + 1}条脚本 执行成功 O(∩_∩)O !`)
+ }
+}
+
+// 获取所有sql脚本内容
+let sqlContentMap = getSqlContentMap()
+
+// 执行建表sql脚本
+const createAllTables = async () => {
+ for( let key in sqlContentMap ) {
+ let sqlShell = sqlContentMap[key]
+ let sqlShellList = sqlShell.split(';')
+
+ for ( let [ i, shell ] of sqlShellList.entries() ) {
+ if ( shell.trim() ) {
+ let result = await query( shell )
+ if ( result.serverStatus * 1 === 2 ) {
+ eventLog( null, key, i)
+ } else {
+ eventLog( true, key, i)
+ }
+ }
+ }
+ }
+ console.log('sql脚本执行结束!')
+ console.log('请按 ctrl + c 键退出!')
+
+}
+
+createAllTables()
+```
+
+### sql脚本文件 ./sql/data.sql
+```sql
+CREATE TABLE IF NOT EXISTS `data` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `data_info` json DEFAULT NULL,
+ `create_time` varchar(20) DEFAULT NULL,
+ `modified_time` varchar(20) DEFAULT NULL,
+ `level` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8
+```
+
+### sql脚本文件 ./sql/user.sql
+```sql
+CREATE TABLE IF NOT EXISTS `user` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `email` varchar(255) DEFAULT NULL,
+ `password` varchar(255) DEFAULT NULL,
+ `name` varchar(255) DEFAULT NULL,
+ `nick` varchar(255) DEFAULT NULL,
+ `detail_info` json DEFAULT NULL,
+ `create_time` varchar(20) DEFAULT NULL,
+ `modified_time` varchar(20) DEFAULT NULL,
+ `level` int(11) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+INSERT INTO `user` set email='1@example.com', password='123456';
+INSERT INTO `user` set email='2@example.com', password='123456';
+INSERT INTO `user` set email='3@example.com', password='123456';
+```
+
+
+## 效果
+
+### 执行脚本
+```
+node --harmony index.js
+```
+
+### 执行结果
+![mysql-init-result-01](./../images/mysql-init-result-01.png)
+
+### 查看数据库写入数据
+![mysql-init-result-01](./../images/mysql-init-result-02.png)
+
diff --git a/note/project/framework.md b/note/project/framework.md
new file mode 100644
index 0000000..ff13a08
--- /dev/null
+++ b/note/project/framework.md
@@ -0,0 +1,91 @@
+# 框架设计
+
+## 实现概要
+- koa2 搭建服务
+- MySQL作为数据库
+ - mysql 5.7 版本
+ - 储存普通数据
+ - 存储session登录态数据
+- 渲染
+ - 服务端渲染:ejs作为服务端渲染的模板引擎
+ - 前端渲染:用webpack2环境编译react.js动态渲染页面,使用ani-design框架
+
+## 文件目录设计
+```sh
+├── init # 数据库初始化目录
+│ ├── index.js # 初始化入口文件
+│ ├── sql/ # sql脚本文件目录
+│ └── util/ # 工具操作目录
+├── package.json
+├── config.js # 配置文件
+├── server # 后端代码目录
+│ ├── app.js # 后端服务入口文件
+│ ├── codes/ # 提示语代码目录
+│ ├── controllers/ # 操作层目录
+│ ├── models/ # 数据模型model层目录
+│ ├── routers/ # 路由目录
+│ ├── services/ # 业务层目录
+│ ├── utils/ # 工具类目录
+│ └── views/ # 模板目录
+└── static # 前端静态代码目录
+ ├── build/ # webpack编译配置目录
+ ├── output/ # 编译后前端代码目录&静态资源前端访问目录
+ └── src/ # 前端源代码目录
+```
+
+## 入口文件预览
+
+```js
+const path = require('path')
+const Koa = require('koa')
+const convert = require('koa-convert')
+const views = require('koa-views')
+const koaStatic = require('koa-static')
+const bodyParser = require('koa-bodyparser')
+const koaLogger = require('koa-logger')
+const session = require('koa-session-minimal')
+const MysqlStore = require('koa-mysql-session')
+
+const config = require('./../config')
+const routers = require('./routers/index')
+
+const app = new Koa()
+
+// session存储配置
+const sessionMysqlConfig= {
+ user: config.database.USERNAME,
+ password: config.database.PASSWORD,
+ database: config.database.DATABASE,
+ host: config.database.HOST,
+}
+
+// 配置session中间件
+app.use(session({
+ key: 'USER_SID',
+ store: new MysqlStore(sessionMysqlConfig)
+}))
+
+// 配置控制台日志中间件
+app.use(convert(koaLogger()))
+
+// 配置ctx.body解析中间件
+app.use(bodyParser())
+
+// 配置静态资源加载中间件
+app.use(convert(koaStatic(
+ path.join(__dirname , './../static')
+)))
+
+// 配置服务端模板渲染引擎中间件
+app.use(views(path.join(__dirname, './views'), {
+ extension: 'ejs'
+}))
+
+// 初始化路由中间件
+app.use(routers.routes()).use(routers.allowedMethods())
+
+// 监听启动端口
+app.listen( config.port )
+console.log(`the server is start at port ${config.port}`)
+
+```
\ No newline at end of file
diff --git a/note/project/layer.md b/note/project/layer.md
new file mode 100644
index 0000000..f1f6c22
--- /dev/null
+++ b/note/project/layer.md
@@ -0,0 +1,28 @@
+# 分层设计
+
+## 后端代码目录
+```sh
+└── server
+ ├── controllers # 操作层 执行服务端模板渲染,json接口返回数据,页面跳转
+ │ ├── admin.js
+ │ ├── index.js
+ │ ├── user-info.js
+ │ └── work.js
+ ├── models # 数据模型层 执行数据操作
+ │ └── user-Info.js
+ ├── routers # 路由层 控制路由
+ │ ├── admin.js
+ │ ├── api.js
+ │ ├── error.js
+ │ ├── home.js
+ │ ├── index.js
+ │ └── work.js
+ ├── services # 业务层 实现数据层model到操作层controller的耦合封装
+ │ └── user-info.js
+ └── views # 服务端模板代码
+ ├── admin.ejs
+ ├── error.ejs
+ ├── index.ejs
+ └── work.ejs
+```
+
diff --git a/note/project/react.md b/note/project/react.md
new file mode 100644
index 0000000..d222ac2
--- /dev/null
+++ b/note/project/react.md
@@ -0,0 +1,107 @@
+# 使用react.js
+
+## react.js简介
+
+react.js 是作为前端渲染的js库(注意:不是框架)。react.js用JSX开发来描述DOM结构,通过编译成virtual dom的在浏览器中进行view渲染和动态交互处理。更多了解可查阅GitHub[https://facebook.github.io/react/](https://facebook.github.io/react/)
+
+## 编译使用
+由于react.js开发过程用JSX编程,无法直接在浏览器中运行,需要编译成浏览器可识别运行的virtual dom。从JSX开发到运行,需要有一个编译的过程。目前最常用的方案是用webpack + babel进行编译打包。
+
+## 前端待编译源文件目录
+demos/project/static/
+```sh
+.
+├── build # 编译的webpack脚本
+│ ├── webpack.base.config.js
+│ ├── webpack.dev.config.js
+│ └── webpack.prod.config.js
+├── output # 输出文件
+│ ├── asset
+│ ├── dist # react.js编译后的文件目录
+│ └── ...
+└── src
+ ├── apps # 页面react.js应用
+ │ ├── admin.jsx
+ │ ├── error.jsx
+ │ ├── index.jsx
+ │ └── work.jsx
+ ├── components # jsx 模块、组件
+ │ ├── footer-common.jsx
+ │ ├── form-group.jsx
+ │ ├── header-nav.jsx
+ │ ├── sign-in-form.jsx
+ │ └── sign-up-form.jsx
+ └── pages # react.js 执行render文件目录
+ ├── admin.js
+ ├── error.js
+ ├── index.js
+ └── work.js
+ ...
+```
+
+
+### react.js页面应用文件
+static/src/apps/index.jsx 文件
+``` jsx
+import React from 'react'
+import ReactDOM from 'react-dom'
+import { Layout, Menu, Breadcrumb } from 'antd'
+import HeadeNav from './../components/header-nav.jsx'
+import FooterCommon from './../components/footer-common.jsx'
+import 'antd/lib/layout/style/css'
+
+const { Header, Content, Footer } = Layout
+
+class App extends React.Component {
+ render() {
+ return (
+
+
+
+
+ Home
+
+
+
+
+
+ )
+ }
+}
+export default App
+```
+
+### react.js执行render渲染
+static/src/pages/index.js 文件
+```js
+import React from 'react'
+import ReactDOM from 'react-dom'
+import App from './../apps/index.jsx'
+
+ReactDOM.render( ,
+ document.getElementById("app"))
+```
+
+### 静态页面引用react.js编译后文件
+```sh
+
+
+
+ <%= title %>
+
+
+
+
+
+
+
+
+```
+
+
+
+### 页面渲染效果
+![project-result-01.png](./../images/project-result-00.png)
+
diff --git a/note/project/route.md b/note/project/route.md
new file mode 100644
index 0000000..36fb0a5
--- /dev/null
+++ b/note/project/route.md
@@ -0,0 +1,74 @@
+# 路由设计
+
+## 使用koa-router中间件
+
+### 路由目录
+```sh
+# ...
+└── server # 后端代码目录
+ └── routers
+ ├── admin.js # /admin/* 子路由
+ ├── api.js # resetful /api/* 子路由
+ ├── error.js # /error/* 子路由
+ ├── home.js # 主页子路由
+ ├── index.js # 子路由汇总文件
+ └── work.js # /work/* 子路由
+ # ...
+```
+
+### 子路由配置
+
+### resetful API 子路由
+例如api子路由/user/getUserInfo.json,整合到主路由,加载到中间件后,请求的路径会是 http://www.example.com/api/user/getUserInfo.json
+
+./demos/project/server/routers/api.js
+```js
+/**
+ * restful api 子路由
+ */
+
+const router = require('koa-router')()
+const userInfoController = require('./../controllers/user-info')
+
+const routers = router
+ .get('/user/getUserInfo.json', userInfoController.getLoginUserInfo)
+ .post('/user/signIn.json', userInfoController.signIn)
+ .post('/user/signUp.json', userInfoController.signUp)
+
+module.exports = routers
+```
+
+#### 子路由汇总
+./demos/project/server/routers/index.js
+```js
+/**
+ * 整合所有子路由
+ */
+
+const router = require('koa-router')()
+
+const home = require('./home')
+const api = require('./api')
+const admin = require('./admin')
+const work = require('./work')
+const error = require('./error')
+
+router.use('/', home.routes(), home.allowedMethods())
+router.use('/api', api.routes(), api.allowedMethods())
+router.use('/admin', admin.routes(), admin.allowedMethods())
+router.use('/work', work.routes(), work.allowedMethods())
+router.use('/error', error.routes(), error.allowedMethods())
+module.exports = router
+```
+
+#### app.js加载路由中间件
+./demos/project/server/app.js
+```js
+const routers = require('./routers/index')
+
+// 初始化路由中间件
+app.use(routers.routes()).use(routers.allowedMethods())
+```
+
+
+
diff --git a/note/project/session.md b/note/project/session.md
new file mode 100644
index 0000000..8e2f74a
--- /dev/null
+++ b/note/project/session.md
@@ -0,0 +1,53 @@
+# session登录态判断处理
+
+## 使用session中间件
+```js
+// code ...
+const session = require('koa-session-minimal')
+const MysqlStore = require('koa-mysql-session')
+
+const config = require('./../config')
+
+// code ...
+
+const app = new Koa()
+
+// session存储配置
+const sessionMysqlConfig= {
+ user: config.database.USERNAME,
+ password: config.database.PASSWORD,
+ database: config.database.DATABASE,
+ host: config.database.HOST,
+}
+
+// 配置session中间件
+app.use(session({
+ key: 'USER_SID',
+ store: new MysqlStore(sessionMysqlConfig)
+}))
+// code ...
+```
+
+## 登录成功后设置session到MySQL和设置sessionId到cookie
+```js
+let session = ctx.session
+session.isLogin = true
+session.userName = userResult.name
+session.userId = userResult.id
+```
+
+## 需要判断登录态页面进行session判断
+```js
+async indexPage ( ctx ) {
+ // 判断是否有session
+ if ( ctx.session && ctx.session.isLogin && ctx.session.userName ) {
+ const title = 'work页面'
+ await ctx.render('work', {
+ title,
+ })
+ } else {
+ // 没有登录态则跳转到错误页面
+ ctx.redirect('/error')
+ }
+ },
+```
diff --git a/note/project/sign.md b/note/project/sign.md
new file mode 100644
index 0000000..8592912
--- /dev/null
+++ b/note/project/sign.md
@@ -0,0 +1,279 @@
+# 登录注册功能实现
+
+## 用户模型dao操作
+```js
+/**
+ * 数据库创建用户
+ * @param {object} model 用户数据模型
+ * @return {object} mysql执行结果
+ */
+ async create ( model ) {
+ let result = await dbUtils.insertData( 'user_info', model )
+ return result
+ },
+
+ /**
+ * 查找一个存在用户的数据
+ * @param {obejct} options 查找条件参数
+ * @return {object|null} 查找结果
+ */
+ async getExistOne(options ) {
+ let _sql = `
+ SELECT * from user_info
+ where email="${options.email}" or name="${options.name}"
+ limit 1`
+ let result = await dbUtils.query( _sql )
+ if ( Array.isArray(result) && result.length > 0 ) {
+ result = result[0]
+ } else {
+ result = null
+ }
+ return result
+ },
+
+ /**
+ * 根据用户名和密码查找用户
+ * @param {object} options 用户名密码对象
+ * @return {object|null} 查找结果
+ */
+ async getOneByUserNameAndPassword( options ) {
+ let _sql = `
+ SELECT * from user_info
+ where password="${options.password}" and name="${options.name}"
+ limit 1`
+ let result = await dbUtils.query( _sql )
+ if ( Array.isArray(result) && result.length > 0 ) {
+ result = result[0]
+ } else {
+ result = null
+ }
+ return result
+ },
+
+ /**
+ * 根据用户名查找用户信息
+ * @param {string} userName 用户账号名称
+ * @return {object|null} 查找结果
+ */
+ async getUserInfoByUserName( userName ) {
+
+ let result = await dbUtils.select(
+ 'user_info',
+ ['id', 'email', 'name', 'detail_info', 'create_time', 'modified_time', 'modified_time' ])
+ if ( Array.isArray(result) && result.length > 0 ) {
+ result = result[0]
+ } else {
+ result = null
+ }
+ return result
+ },
+
+```
+
+
+### 业务层操作
+```js
+/**
+ * 创建用户
+ * @param {object} user 用户信息
+ * @return {object} 创建结果
+ */
+ async create( user ) {
+ let result = await userModel.create(user)
+ return result
+ },
+
+ /**
+ * 查找存在用户信息
+ * @param {object} formData 查找的表单数据
+ * @return {object|null} 查找结果
+ */
+ async getExistOne( formData ) {
+ let resultData = await userModel.getExistOne({
+ 'email': formData.email,
+ 'name': formData.userName
+ })
+ return resultData
+ },
+
+ /**
+ * 登录业务操作
+ * @param {object} formData 登录表单信息
+ * @return {object} 登录业务操作结果
+ */
+ async signIn( formData ) {
+ let resultData = await userModel.getOneByUserNameAndPassword({
+ 'password': formData.password,
+ 'name': formData.userName})
+ return resultData
+ },
+
+
+ /**
+ * 根据用户名查找用户业务操作
+ * @param {string} userName 用户名
+ * @return {object|null} 查找结果
+ */
+ async getUserInfoByUserName( userName ) {
+
+ let resultData = await userModel.getUserInfoByUserName( userName ) || {}
+ let userInfo = {
+ // id: resultData.id,
+ email: resultData.email,
+ userName: resultData.name,
+ detailInfo: resultData.detail_info,
+ createTime: resultData.create_time
+ }
+ return userInfo
+ },
+
+
+ /**
+ * 检验用户注册数据
+ * @param {object} userInfo 用户注册数据
+ * @return {object} 校验结果
+ */
+ validatorSignUp( userInfo ) {
+ let result = {
+ success: false,
+ message: '',
+ }
+
+ if ( /[a-z0-9\_\-]{6,16}/.test(userInfo.userName) === false ) {
+ result.message = userCode.ERROR_USER_NAME
+ return result
+ }
+ if ( !validator.isEmail( userInfo.email ) ) {
+ result.message = userCode.ERROR_EMAIL
+ return result
+ }
+ if ( !/[\w+]{6,16}/.test( userInfo.password ) ) {
+ result.message = userCode.ERROR_PASSWORD
+ return result
+ }
+ if ( userInfo.password !== userInfo.confirmPassword ) {
+ result.message = userCode.ERROR_PASSWORD_CONFORM
+ return result
+ }
+
+ result.success = true
+
+ return result
+ }
+```
+
+### controller 操作
+```js
+ /**
+ * 登录操作
+ * @param {obejct} ctx 上下文对象
+ */
+ async signIn( ctx ) {
+ let formData = ctx.request.body
+ let result = {
+ success: false,
+ message: '',
+ data: null,
+ code: ''
+ }
+
+ let userResult = await userInfoService.signIn( formData )
+
+ if ( userResult ) {
+ if ( formData.userName === userResult.name ) {
+ result.success = true
+ } else {
+ result.message = userCode.FAIL_USER_NAME_OR_PASSWORD_ERROR
+ result.code = 'FAIL_USER_NAME_OR_PASSWORD_ERROR'
+ }
+ } else {
+ result.code = 'FAIL_USER_NO_EXIST',
+ result.message = userCode.FAIL_USER_NO_EXIST
+ }
+
+ if ( formData.source === 'form' && result.success === true ) {
+ let session = ctx.session
+ session.isLogin = true
+ session.userName = userResult.name
+ session.userId = userResult.id
+
+ ctx.redirect('/work')
+ } else {
+ ctx.body = result
+ }
+ },
+
+ /**
+ * 注册操作
+ * @param {obejct} ctx 上下文对象
+ */
+ async signUp( ctx ) {
+ let formData = ctx.request.body
+ let result = {
+ success: false,
+ message: '',
+ data: null
+ }
+
+ let validateResult = userInfoService.validatorSignUp( formData )
+
+ if ( validateResult.success === false ) {
+ result = validateResult
+ ctx.body = result
+ return
+ }
+
+ let existOne = await userInfoService.getExistOne(formData)
+ console.log( existOne )
+
+ if ( existOne ) {
+ if ( existOne .name === formData.userName ) {
+ result.message = userCode.FAIL_USER_NAME_IS_EXIST
+ ctx.body = result
+ return
+ }
+ if ( existOne .email === formData.email ) {
+ result.message = userCode.FAIL_EMAIL_IS_EXIST
+ ctx.body = result
+ return
+ }
+ }
+
+
+ let userResult = await userInfoService.create({
+ email: formData.email,
+ password: formData.password,
+ name: formData.userName,
+ create_time: new Date().getTime(),
+ level: 1,
+ })
+
+ console.log( userResult )
+
+ if ( userResult && userResult.insertId * 1 > 0) {
+ result.success = true
+ } else {
+ result.message = userCode.ERROR_SYS
+ }
+
+ ctx.body = result
+ },
+```
+
+### api路由操作
+```js
+const router = require('koa-router')()
+const userInfoController = require('./../controllers/user-info')
+
+const routers = router
+ .get('/user/getUserInfo.json', userInfoController.getLoginUserInfo)
+ .post('/user/signIn.json', userInfoController.signIn)
+ .post('/user/signUp.json', userInfoController.signUp)
+```
+
+## 前端用react.js实现效果
+
+登录模式
+![project-result-01](./../images/project-result-01.png)
+注册模式
+![project-result-01](./../images/project-result-02.png)
diff --git a/note/project/sql.md b/note/project/sql.md
new file mode 100644
index 0000000..6d0d51d
--- /dev/null
+++ b/note/project/sql.md
@@ -0,0 +1,25 @@
+# 数据库设计
+
+## 初始化数据库脚本
+
+### 脚本目录
+./demos/project/init/sql/
+
+```sql
+CREATE TABLE IF NOT EXISTS `user_info` (
+ `id` int(11) NOT NULL AUTO_INCREMENT, # 用户ID
+ `email` varchar(255) DEFAULT NULL, # 邮箱地址
+ `password` varchar(255) DEFAULT NULL, # 密码
+ `name` varchar(255) DEFAULT NULL, # 用户名
+ `nick` varchar(255) DEFAULT NULL, # 用户昵称
+ `detail_info` longtext DEFAULT NULL, # 详细信息
+ `create_time` varchar(20) DEFAULT NULL, # 创建时间
+ `modified_time` varchar(20) DEFAULT NULL, # 修改时间
+ `level` int(11) DEFAULT NULL, # 权限级别
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+# 插入默认信息
+INSERT INTO `user_info` set name='admin001', email='admin001@example.com', password='123456';
+```
+
diff --git a/note/project/webpack2.md b/note/project/webpack2.md
new file mode 100644
index 0000000..14c4fc2
--- /dev/null
+++ b/note/project/webpack2.md
@@ -0,0 +1,162 @@
+# webpack2 环境搭建
+
+## 前言
+由于demos/project 前端渲染是通过react.js渲染的,这就需要webpack2 对react.js及其相关JSX,ES6/7代码进行编译和混淆压缩。
+
+## webpack2
+### 安装和文档
+可访问网[https://webpack.js.org/](https://webpack.js.org/)
+
+## 配置webpack2编译react.js + less + sass + antd 环境
+
+### 文件目录
+```sh
+└── static # 项目静态文件目录
+ ├── build
+ │ ├── webpack.base.config.js # 基础编译脚本
+ │ ├── webpack.dev.config.js # 开发环境编译脚本
+ │ └── webpack.prod.config.js # 生产环境编译脚本
+ ├── output # 编译后输出目录
+ │ ├── asset
+ │ ├── dist
+ │ └── upload
+ └── src # 待编译的ES6/7、JSX源代码
+ ├── api
+ ├── apps
+ ├── components
+ ├── pages
+ ├── texts
+ └── utils
+```
+
+### webpack2 编译基础配置
+
+#### webpack.base.config.js
+```js
+const webpack = require('webpack');
+const ExtractTextPlugin = require('extract-text-webpack-plugin');
+const path = require('path');
+const sourcePath = path.join(__dirname, './static/src');
+const outputPath = path.join(__dirname, './../output/dist/');
+
+module.exports = {
+ // 入口文件
+ entry: {
+ 'admin' : './static/src/pages/admin.js',
+ 'work' : './static/src/pages/work.js',
+ 'index' : './static/src/pages/index.js',
+ 'error' : './static/src/pages/error.js',
+ vendor: ['react', 'react-dom', 'whatwg-fetch'],
+ },
+ // 出口文件
+ output: {
+ path: outputPath,
+ publicPath: '/static/output/dist/',
+ filename: 'js/[name].js',
+ },
+ module: {
+ // 配置编译打包规则
+ rules: [
+ {
+ test: /\.(js|jsx)$/,
+ exclude: /node_modules/,
+ use: [
+ {
+ loader: 'babel-loader',
+ query: {
+ // presets: ['es2015', 'react'],
+ cacheDirectory: true
+ }
+ }
+ ]
+ },
+ {
+ test: /\.css$/,
+ use: ExtractTextPlugin.extract({
+ fallback: "style-loader",
+ use: ['css-loader']
+ }),
+ },
+ {
+ test: /\.scss$/,
+ use: ExtractTextPlugin.extract({
+ fallback: "style-loader",
+ use: ['css-loader', 'sass-loader']
+ })
+ },
+ {
+ test: /\.less$/,
+ use: ExtractTextPlugin.extract({
+ fallback: "style-loader",
+ use: ['css-loader', 'less-loader']
+ })
+ },
+ ]
+ },
+ resolve: {
+ extensions: ['.js', '.jsx'],
+ modules: [
+ sourcePath,
+ 'node_modules'
+ ]
+ },
+ plugins: [
+ new ExtractTextPlugin('css/[name].css'),
+ new webpack.optimize.CommonsChunkPlugin({
+ names: ['vendor'],
+ minChunks: Infinity,
+ filename: 'js/[name].js'
+ }),
+ ]
+};
+```
+
+
+### 配置开发&生产环境webpack2 编译设置
+为了方便编译基本配置代码统一管理,开发环境(wepack.dev.config.js)和生产环境(webpack.prod.config.js)的编译配置都是继承了基本配置(wepack.base.config.js)的代码
+
+#### 开发环境配置 wepack.dev.config.js
+```js
+var merge = require('webpack-merge')
+var webpack = require('webpack')
+var baseWebpackConfig = require('./webpack.base.config');
+
+module.exports = merge(baseWebpackConfig, {
+
+ devtool: 'source-map',
+ plugins: [
+
+ new webpack.DefinePlugin({
+ 'process.env': {
+ NODE_ENV: JSON.stringify('development')
+ }
+ }),
+ ]
+})
+```
+
+#### 编译环境配置 wepack.prod.config.js
+```js
+var webpack = require('webpack');
+var merge = require('webpack-merge');
+var baseWebpackConfig = require('./webpack.base.config');
+
+module.exports = merge(baseWebpackConfig, {
+ // eval-source-map is faster for development
+
+ plugins: [
+
+ new webpack.DefinePlugin({
+ 'process.env': {
+ NODE_ENV: JSON.stringify('production')
+ }
+ }),
+ new webpack.optimize.UglifyJsPlugin({
+ minimize: true,
+ compress: {
+ warnings: false,
+ }
+ })
+ ]
+})
+```
diff --git a/note/request/get.md b/note/request/get.md
new file mode 100644
index 0000000..a083330
--- /dev/null
+++ b/note/request/get.md
@@ -0,0 +1,56 @@
+# GET请求数据获取
+
+## 使用方法
+在koa中,获取GET请求数据源头是koa中request对象中的query方法或querystring方法,query返回是格式化好的参数对象,querystring返回的是请求字符串,由于ctx对request的API有直接引用的方式,所以获取GET请求数据有两个途径。
+- 1.是从上下文中直接获取
+ - 请求对象ctx.query,返回如 { a:1, b:2 }
+ - 请求字符串 ctx.querystring,返回如 a=1&b=2
+- 2.是从上下文的request对象中获取
+ - 请求对象ctx.request.query,返回如 { a:1, b:2 }
+ - 请求字符串 ctx.request.querystring,返回如 a=1&b=2
+
+## 举个例子
+
+源码在 /demos/request/get.js中
+
+### 例子代码
+```js
+const Koa = require('koa')
+const app = new Koa()
+
+const Koa = require('koa')
+const app = new Koa()
+
+app.use( async ( ctx ) => {
+ let url = ctx.url
+ // 从上下文的request对象中获取
+ let request = ctx.request
+ let req_query = request.query
+ let req_querystring = request.querystring
+
+ // 从上下文中直接获取
+ let ctx_query = ctx.query
+ let ctx_querystring = ctx.querystring
+
+ ctx.body = {
+ url,
+ req_query,
+ req_querystring,
+ ctx_query,
+ ctx_querystring
+ }
+})
+
+app.listen(3000)
+console.log('[demo] request get is starting at port 3000')
+
+```
+
+### 执行程序
+```sh
+node --harmony get.js
+```
+执行后程序后,用chrome访问 [http://localhost:3000/page/user?a=1&b=2](http://localhost:3000/page/user?a=1&b=2) 会出现以下情况
+> 注意:我是用了chrome的json格式化插件才会显示json的格式化
+
+![request-get](./../images/request-get.png)
diff --git a/note/request/post-use-middleware.md b/note/request/post-use-middleware.md
new file mode 100644
index 0000000..211c36d
--- /dev/null
+++ b/note/request/post-use-middleware.md
@@ -0,0 +1,68 @@
+# koa-bodyparser中间件
+
+## 原理
+对于POST请求的处理,koa-bodyparser中间件可以把koa2上下文的formData数据解析到ctx.request.body中
+
+### 安装koa2版本的koa-bodyparser@3中间件
+```sh
+npm install --save koa-bodyparser@3
+```
+
+## 举个例子
+源码在 /demos/request/post-middleware.js中
+
+### 例子代码
+```js
+const Koa = require('koa')
+const app = new Koa()
+const bodyParser = require('koa-bodyparser')
+
+// 使用ctx.body解析中间件
+const Koa = require('koa')
+const app = new Koa()
+const bodyParser = require('koa-bodyparser')
+
+// 使用ctx.body解析中间件
+app.use(bodyParser())
+
+app.use( async ( ctx ) => {
+
+ if ( ctx.url === '/' && ctx.method === 'GET' ) {
+ // 当GET请求时候返回表单页面
+ let html = `
+ koa2 request post demo
+
+ `
+ ctx.body = html
+ } else if ( ctx.url === '/' && ctx.method === 'POST' ) {
+ // 当POST请求的时候,中间件koa-bodyparser解析POST表单里的数据,并显示出来
+ let postData = ctx.request.body
+ ctx.body = postData
+ } else {
+ // 其他请求显示404
+ ctx.body = '404!!! o(╯□╰)o
'
+ }
+})
+
+app.listen(3000)
+console.log('[demo] request post is starting at port 3000')
+```
+
+### 启动例子
+```sh
+node --harmony post-middleware.js
+```
+
+### 访问页面
+![request-post-form](./../images/request-post-form.png)
+
+### 提交表单发起POST请求结果显示
+![request-post-result](./../images/request-post-result.png)
diff --git a/note/request/post.md b/note/request/post.md
new file mode 100644
index 0000000..e97cafd
--- /dev/null
+++ b/note/request/post.md
@@ -0,0 +1,122 @@
+# POST请求参数获取
+
+## 原理
+对于POST请求的处理,koa2没有封装获取参数的方法,需要通过解析上下文context中的原生node.js请求对象req,将POST表单数据解析成query string(例如:`a=1&b=2&c=3`),再将query string 解析成JSON格式(例如:`{"a":"1", "b":"2", "c":"3"}`)
+
+> 注意:ctx.request是context经过封装的请求对象,ctx.req是context提供的node.js原生HTTP请求对象,同理ctx.response是context经过封装的响应对象,ctx.res是context提供的node.js原生HTTP请求对象。
+
+> 具体koa2 API文档可见 [https://github.com/koajs/koa/blob/master/docs/api/context.md#ctxreq](https://github.com/koajs/koa/blob/master/docs/api/context.md#ctxreq)
+
+### 解析出POST请求上下文中的表单数据
+
+```js
+// 解析上下文里node原生请求的POST参数
+function parsePostData( ctx ) {
+ return new Promise((resolve, reject) => {
+ try {
+ let postdata = "";
+ ctx.req.addListener('data', (data) => {
+ postdata += data
+ })
+ ctx.req.addListener("end",function(){
+ let parseData = parseQueryStr( postdata )
+ resolve( parseData )
+ })
+ } catch ( err ) {
+ reject(err)
+ }
+ })
+}
+
+// 将POST请求参数字符串解析成JSON
+function parseQueryStr( queryStr ) {
+ let queryData = {}
+ let queryStrList = queryStr.split('&')
+ console.log( queryStrList )
+ for ( let [ index, queryStr ] of queryStrList.entries() ) {
+ let itemList = queryStr.split('=')
+ queryData[ itemList[0] ] = decodeURIComponent(itemList[1])
+ }
+ return queryData
+}
+```
+
+## 举个例子
+源码在 /demos/request/post.js中
+### 例子代码
+```js
+const Koa = require('koa')
+const app = new Koa()
+
+app.use( async ( ctx ) => {
+
+ if ( ctx.url === '/' && ctx.method === 'GET' ) {
+ // 当GET请求时候返回表单页面
+ let html = `
+ koa2 request post demo
+
+ `
+ ctx.body = html
+ } else if ( ctx.url === '/' && ctx.method === 'POST' ) {
+ // 当POST请求的时候,解析POST表单里的数据,并显示出来
+ let postData = await parsePostData( ctx )
+ ctx.body = postData
+ } else {
+ // 其他请求显示404
+ ctx.body = '404!!! o(╯□╰)o
'
+ }
+})
+
+// 解析上下文里node原生请求的POST参数
+function parsePostData( ctx ) {
+ return new Promise((resolve, reject) => {
+ try {
+ let postdata = "";
+ ctx.req.addListener('data', (data) => {
+ postdata += data
+ })
+ ctx.req.addListener("end",function(){
+ let parseData = parseQueryStr( postdata )
+ resolve( parseData )
+ })
+ } catch ( err ) {
+ reject(err)
+ }
+ })
+}
+
+// 将POST请求参数字符串解析成JSON
+function parseQueryStr( queryStr ) {
+ let queryData = {}
+ let queryStrList = queryStr.split('&')
+ console.log( queryStrList )
+ for ( let [ index, queryStr ] of queryStrList.entries() ) {
+ let itemList = queryStr.split('=')
+ queryData[ itemList[0] ] = decodeURIComponent(itemList[1])
+ }
+ return queryData
+}
+
+app.listen(3000)
+console.log('[demo] request post is starting at port 3000')
+
+```
+
+### 启动例子
+```sh
+node --harmony post.js
+```
+
+### 访问页面
+![request-post-form](./../images/request-post-form.png)
+
+### 提交表单发起POST请求结果显示
+![request-post-result](./../images/request-post-result.png)
diff --git a/note/route/koa-router.md b/note/route/koa-router.md
new file mode 100644
index 0000000..0d1b39b
--- /dev/null
+++ b/note/route/koa-router.md
@@ -0,0 +1,51 @@
+# koa-router中间件
+
+如果依靠ctx.request.url去手动处理路由,将会写很多处理代码,这时候就需要对应的路由的中间件对路由进行控制,这里介绍一个比较好用的路由中间件koa-router
+
+## 安装koa-router中间件
+```sh
+# koa2 对应的版本是 7.x
+npm install --save koa-router@7
+```
+
+## 快速使用koa-router
+```js
+const Koa = require('koa')
+const fs = require('fs')
+const app = new Koa()
+
+const Router = require('koa-router')
+
+let home = new Router()
+
+// 子路由1
+home.get('/', async ( ctx )=>{
+ let html = `
+
+ `
+ ctx.body = html
+})
+
+// 子路由2
+let page = new Router()
+page.get('/404', async ( ctx )=>{
+ ctx.body = '404 page!'
+}).get('/helloworld', async ( ctx )=>{
+ ctx.body = 'helloworld page!'
+})
+
+// 装载所有子路由
+let router = new Router()
+router.use('/', home.routes(), home.allowedMethods())
+router.use('/page', page.routes(), page.allowedMethods())
+
+// 加载路由中间件
+app.use(router.routes()).use(router.allowedMethods())
+
+app.listen(3000)
+console.log('[demo] route-use-middleware is starting at port 3000')
+
+```
diff --git a/note/route/simple.md b/note/route/simple.md
new file mode 100644
index 0000000..6a9cd58
--- /dev/null
+++ b/note/route/simple.md
@@ -0,0 +1,103 @@
+# koa2 原生路由实现
+
+
+## 简单例子
+```js
+const Koa = require('koa')
+const app = new Koa()
+
+app.use( async ( ctx ) => {
+ let url = ctx.request.url
+ ctx.body = url
+})
+app.listen(3000)
+```
+
+访问 http://localhost:3000/hello/world 页面会输出 /hello/world,也就是说上下文的请求request对象中url之就是当前访问的路径名称,可以根据ctx.request.url 通过一定的判断或者正则匹配就可以定制出所需要的路由。
+
+
+## 定制化的路由
+
+### 源码文件目录
+```
+.
+├── index.js
+├── package.json
+└── view
+ ├── 404.html
+ ├── index.html
+ └── todo.html
+```
+
+### demo源码
+
+```js
+const Koa = require('koa')
+const fs = require('fs')
+const app = new Koa()
+
+/**
+ * 用Promise封装异步读取文件方法
+ * @param {string} page html文件名称
+ * @return {promise}
+ */
+function render( page ) {
+ return new Promise(( resolve, reject ) => {
+ let viewUrl = `./view/${page}`
+ fs.readFile(viewUrl, "binary", ( err, data ) => {
+ if ( err ) {
+ reject( err )
+ } else {
+ resolve( data )
+ }
+ })
+ })
+}
+
+/**
+ * 根据URL获取HTML内容
+ * @param {string} url koa2上下文的url,ctx.url
+ * @return {string} 获取HTML文件内容
+ */
+async function route( url ) {
+ let view = '404.html'
+ switch ( url ) {
+ case '/':
+ view = 'index.html'
+ break
+ case '/index':
+ view = 'index.html'
+ break
+ case '/todo':
+ view = 'todo.html'
+ break
+ case '/404':
+ view = '404.html'
+ break
+ default:
+ break
+ }
+ let html = await render( view )
+ return html
+}
+
+app.use( async ( ctx ) => {
+ let url = ctx.request.url
+ let html = await route( url )
+ ctx.body = html
+})
+
+app.listen(3000)
+console.log('[demo] route-simple is starting at port 3000')
+
+```
+
+### 运行demo
+#### 执行运行脚本
+```sh
+node -harmony index.js
+```
+
+#### 运行效果如下
+访问[http://localhost:3000/index](http://localhost:3000/index)
+![route-result-01](./../images/route-result-01.png)
\ No newline at end of file
diff --git a/note/session/info.md b/note/session/info.md
new file mode 100644
index 0000000..30be04c
--- /dev/null
+++ b/note/session/info.md
@@ -0,0 +1,96 @@
+# koa2实现session
+
+## 前言
+koa2原生功能只提供了cookie的操作,但是没有提供session操作。session就只用自己实现或者通过第三方中间件实现。在koa2中实现session的方案有一下几种
+- 如果session数据量很小,可以直接存在内存中
+- 如果session数据量很大,则需要存储介质存放session数据
+
+## 数据库存储方案
+- 将session存放在MySQL数据库中
+- 需要用到中间件
+ - koa-session-minimal 适用于koa2 的session中间件,提供存储介质的读写接口 。
+ - koa-mysql-session 为koa-session-minimal中间件提供MySQL数据库的session数据读写操作。
+ - 将sessionId和对于的数据存到数据库
+- 将数据库的存储的sessionId存到页面的cookie中
+- 根据cookie的sessionId去获取对于的session信息
+
+## 快速使用
+### 例子代码
+```js
+const Koa = require('koa')
+const session = require('koa-session-minimal')
+const MysqlSession = require('koa-mysql-session')
+
+const app = new Koa()
+
+// 配置存储session信息的mysql
+let store = new MysqlSession({
+ user: 'root',
+ password: 'abc123',
+ database: 'koa_demo',
+ host: '127.0.0.1',
+})
+
+// 存放sessionId的cookie配置
+let cookie = {
+ maxAge: '', // cookie有效时长
+ expires: '', // cookie失效时间
+ path: '', // 写cookie所在的路径
+ domain: '', // 写cookie所在的域名
+ httpOnly: '', // 是否只用于http请求中获取
+ overwrite: '', // 是否允许重写
+ secure: '',
+ sameSite: '',
+ signed: '',
+
+}
+
+// 使用session中间件
+app.use(session({
+ key: 'SESSION_ID',
+ store: store,
+ cookie: cookie
+}))
+
+app.use( async ( ctx ) => {
+
+ // 设置session
+ if ( ctx.url === '/set' ) {
+ ctx.session = {
+ user_id: Math.random().toString(36).substr(2),
+ count: 0
+ }
+ ctx.body = ctx.session
+ } else if ( ctx.url === '/' ) {
+
+ // 读取session信息
+ ctx.session.count = ctx.session.count + 1
+ ctx.body = ctx.session
+ }
+
+})
+
+app.listen(3000)
+console.log('[demo] session is starting at port 3000')
+```
+
+### 运行例子
+#### 执行命令
+```sh
+node --harmony index.js
+```
+
+#### 访问连接设置session
+[http://localhost:3000/set](http://localhost:3000/set)
+![session-result-01](./../images/session-result-01.png)
+
+#### 查看数据库session是否存储
+![session-result-01](./../images/session-result-03.png)
+
+#### 查看cookie中是否种下了sessionId
+[http://localhost:3000](http://localhost:3000)
+![session-result-01](./../images/session-result-02.png)
+
+
+
+
diff --git a/note/start/async.md b/note/start/async.md
new file mode 100644
index 0000000..21d4d8c
--- /dev/null
+++ b/note/start/async.md
@@ -0,0 +1,44 @@
+# async/await使用
+
+## 快速上手理解
+
+ 先复制以下这段代码,在粘贴在chrome的控制台console中,按回车键执行
+
+``` js
+function getSyncTime() {
+ return new Promise((resolve, reject) => {
+ try {
+ let startTime = new Date().getTime()
+ setTimeout(() => {
+ let endTime = new Date().getTime()
+ let data = endTime - startTime
+ resolve( data )
+ }, 500)
+ } catch ( err ) {
+ reject( err )
+ }
+ })
+}
+
+async function getSyncData() {
+ let time = await getSyncTime()
+ let data = `endTime - startTime = ${time}`
+ return data
+}
+
+async function getData() {
+ let data = await getSyncData()
+ console.log( data )
+}
+
+getData()
+
+```
+
+### 在chrome的console中执行结果如下
+![async](./../images/async.png)
+
+### 从上述例子可以看出 async/await 的特点:
+- 可以让异步逻辑用同步写法实现
+- 最底层的await返回需要是Promise对象
+- 可以通过多层 async function 的同步写法代替传统的callback嵌套
diff --git a/note/start/info.md b/note/start/info.md
new file mode 100644
index 0000000..cdc3091
--- /dev/null
+++ b/note/start/info.md
@@ -0,0 +1,23 @@
+# koa2简析结构
+
+## 源码文件
+```
+├── lib
+│ ├── application.js
+│ ├── context.js
+│ ├── request.js
+│ └── response.js
+└── package.json
+```
+这个就是GitHub[https://github.com/koajs/koa/tree/v2.x](https://github.com/koajs/koa/tree/v2.x)上开源的koa2源码的源文件结构,核心代码就是lib目录下的四个文件
+
+- application.js 是整个koa2 的入口文件,封装了context,request,response,以及最核心的中间件处理流程。
+- context.js 处理应用上下文,里面直接封装部分request.js和response.js的方法
+- request.js 处理http请求
+- response.js 处理http响应
+
+## koa2特性
+
+- 只提供封装好http上下文、请求、响应,以及基于`async/await`的中间件容器。
+- 利用ES7的`async/await`的来处理传统回调嵌套问题和代替koa@1的generator,但是需要在node.js 7.x的harmony模式下才能支持`async/await`。
+- 中间件只支持 `async/await` 封装的,如果要使用koa@1基于generator中间件,需要通过中间件koa-convert封装一下才能使用。
diff --git a/note/start/koa-async.md b/note/start/koa-async.md
new file mode 100644
index 0000000..49fc824
--- /dev/null
+++ b/note/start/koa-async.md
@@ -0,0 +1,45 @@
+# koa2 中的 async/await 使用
+
+## 举个栗子
+
+- Promise封装 fs 异步读取文件的方法
+
+```js
+// code file: util/render.js
+// Promise封装 fs 异步读取文件的方法
+
+const fs = require('fs')
+
+function render( page ) {
+ return new Promise(( resolve, reject ) => {
+ let viewUrl = `./view/${page}`
+ fs.readFile(viewUrl, "binary", ( err, data ) => {
+ if ( err ) {
+ reject( err )
+ } else {
+ resolve( data )
+ }
+ })
+ })
+}
+
+module.exports = render
+```
+
+- koa2 通过async/await 实现读取HTML文件并执行渲染
+
+```js
+// code file : index.js
+// koa2 通过async/await 实现读取HTML文件并执行渲染
+const Koa = require('koa')
+const render = require('./util/render')
+const app = new Koa()
+
+app.use( async ( ctx ) => {
+ let html = await render('index.html')
+ ctx.body = html
+})
+
+app.listen(3000)
+console.log('[demo] start-async is starting at port 3000')
+```
diff --git a/note/start/middleware.md b/note/start/middleware.md
new file mode 100644
index 0000000..5fc27b3
--- /dev/null
+++ b/note/start/middleware.md
@@ -0,0 +1,125 @@
+# koa中间件开发和使用
+
+> 注:原文地址在我的博客issue里[https://github.com/ChenShenhai/blog/issues/15](https://github.com/ChenShenhai/blog/issues/15)
+
+- koa v1和v2中使用到的中间件的开发和使用
+- generator 中间件开发在koa v1和v2中使用
+- async await 中间件开发和只能在koa v2中使用
+
+## generator中间件开发
+
+### generator中间件开发
+
+> generator中间件返回的应该是function * () 函数
+
+```js
+/* ./middleware/logger-generator.js */
+function log( ctx ) {
+ console.log( ctx.method, ctx.header.host + ctx.url )
+}
+
+module.exports = function () {
+ return function * ( next ) {
+
+ // 执行中间件的操作
+ log( this )
+
+ if ( next ) {
+ yield next
+ }
+ }
+}
+```
+
+### generator中间件在koa@1中的使用
+
+> generator 中间件在koa v1中可以直接use使用
+
+```js
+const koa = require('koa') // koa v1
+const loggerGenerator = require('./middleware/logger-generator')
+const app = koa()
+
+app.use(loggerGenerator())
+
+app.use(function *( ) {
+ this.body = 'hello world!'
+})
+
+app.listen(3000)
+console.log('the server is starting at port 3000')
+```
+
+### generator中间件在koa@2中的使用
+> generator 中间件在koa v2中需要用koa-convert封装一下才能使用
+
+```js
+const Koa = require('koa') // koa v2
+const convert = require('koa-convert')
+const loggerGenerator = require('./middleware/logger-generator')
+const app = new Koa()
+
+app.use(convert(loggerGenerator()))
+
+app.use(( ctx ) => {
+ ctx.body = 'hello world!'
+})
+
+app.listen(3000)
+console.log('the server is starting at port 3000')
+```
+
+
+## async中间件开发
+
+### async 中间件开发
+
+
+```js
+/* ./middleware/logger-async.js */
+
+function log( ctx ) {
+ console.log( ctx.method, ctx.header.host + ctx.url )
+}
+
+module.exports = function () {
+
+ return function ( ctx, next ) {
+
+ return new Promise( ( resolve, reject ) => {
+
+ // 执行中间件的操作
+ log( ctx )
+
+ resolve()
+
+ return next()
+
+ }).catch(( err ) => {
+
+ return next()
+ })
+ }
+
+}
+
+```
+
+### async 中间件在koa@2中使用
+
+> async 中间件只能在 koa v2中使用
+
+```js
+const Koa = require('koa') // koa v2
+const loggerAsync = require('./middleware/logger-async')
+const app = new Koa()
+
+app.use(loggerAsync())
+
+app.use(( ctx ) => {
+ ctx.body = 'hello world!'
+})
+
+app.listen(3000)
+console.log('the server is starting at port 3000')
+```
diff --git a/note/start/quick.md b/note/start/quick.md
new file mode 100644
index 0000000..ec7e0b5
--- /dev/null
+++ b/note/start/quick.md
@@ -0,0 +1,52 @@
+# koa2 快速开始
+
+## 环境准备
+- node.js环境 版本v7.x以上
+ - 直接安装node.js 7.x:node.js官网地址[https://nodejs.org](https://nodejs.org)
+ - nvm管理多版本node.js:可以用nvm 进行node版本进行管理
+ - Mac系统安装nvm [https://github.com/creationix/nvm#manual-install](https://github.com/creationix/nvm#manual-install)
+ - windows系统安装nvm [https://github.com/coreybutler/nvm-windows](https://github.com/coreybutler/nvm-windows)
+ - Ubuntu系统安装nvm [https://github.com/creationix/nvm](https://github.com/creationix/nvm)
+- npm 版本3.x以上
+
+
+
+## 快速开始
+
+### 安装koa2
+```sh
+# 初始化package.json
+npm init
+
+# 安装koa2
+# 由于目前koa2 灭有正式发布,属于下一个版本
+npm install koa@next
+
+```
+
+### hello world 代码
+
+```js
+const Koa = require('koa')
+const app = new Koa()
+
+app.use( async ( ctx ) => {
+ ctx.body = 'hello koa2'
+})
+
+app.listen(3000)
+console.log('[demo] start-quick is starting at port 3000')
+```
+
+### 启动demo
+
+由于koa2是基于async/await操作中间件,目前node.js 7.x的harmony模式下才能使用,所以启动的时的脚本如下:
+
+```sh
+node --harmony index.js
+```
+
+访问[http:localhost:3000](http:localhost:3000),效果如下
+
+![start-result-01](./../images/start-result-01.png)
+
diff --git a/note/static/middleware.md b/note/static/middleware.md
new file mode 100644
index 0000000..8276cac
--- /dev/null
+++ b/note/static/middleware.md
@@ -0,0 +1,43 @@
+# koa-static中间件使用
+
+## 使用例子
+
+```js
+const Koa = require('koa')
+const path = require('path')
+const convert = require('koa-convert')
+const static = require('koa-static')
+
+const app = new Koa()
+
+// 静态资源目录对于相对入口文件index.js的路径
+const staticPath = './static'
+
+// 由于koa-static目前不支持koa2
+// 所以只能用koa-convert封装一下
+app.use(convert(static(
+ path.join( __dirname, staticPath)
+)))
+
+
+app.use( async ( ctx ) => {
+ ctx.body = 'hello world'
+})
+
+app.listen(3000)
+console.log('[demo] static-use-middleware is starting at port 3000')
+
+```
+
+#### 效果
+
+##### 访问[http://localhost:3000](http://localhost:3000)
+![static-server-result](./../images/static-server-result-01.png)
+
+##### 访问[http://localhost:3000/index.html](http://localhost:3000/index.html)
+![static-server-result](./../images/static-server-result-02.png)
+
+##### 访问[http://localhost:3000/js/index.js](http://localhost:3000/js/index.js)
+![static-server-result](./../images/static-server-result-03.png)
+
+
diff --git a/note/static/server.md b/note/static/server.md
new file mode 100644
index 0000000..a9eed56
--- /dev/null
+++ b/note/static/server.md
@@ -0,0 +1,269 @@
+# 原生koa2实现静态资源服务器
+
+## 前言
+一个http请求访问web服务静态资源,一般响应结果有三种情况
+- 访问文本,例如js,css,png,jpg,gif
+- 访问静态目录
+- 找不到资源,抛出404错误
+
+## 原生koa2 静态资源服务器例子
+
+### 代码目录
+```sh
+├── static # 静态资源目录
+│ ├── css/
+│ ├── image/
+│ ├── js/
+│ └── index.html
+├── util # 工具代码
+│ ├── content.js # 读取请求内容
+│ ├── dir.js # 读取目录内容
+│ ├── file.js # 读取文件内容
+│ ├── mimes.js # 文件类型列表
+│ └── walk.js # 遍历目录内容
+└── index.js # 启动入口文件
+```
+
+
+### 代码解析
+#### index.js
+```js
+const Koa = require('koa')
+const path = require('path')
+const content = require('./util/content')
+const mimes = require('./util/mimes')
+
+const app = new Koa()
+
+// 静态资源目录对于相对入口文件index.js的路径
+const staticPath = './static'
+
+// 解析资源类型
+function parseMime( url ) {
+ let extName = path.extname( url )
+ extName = extName ? extName.slice(1) : 'unknown'
+ return mimes[ extName ]
+}
+
+app.use( async ( ctx ) => {
+ // 静态资源目录在本地的绝对路径
+ let fullStaticPath = path.join(__dirname, staticPath)
+
+ // 获取静态资源内容,有可能是文件内容,目录,或404
+ let _content = await content( ctx, fullStaticPath )
+
+ // 解析请求内容的类型
+ let _mime = parseMime( ctx.url )
+
+ // 如果有对应的文件类型,就配置上下文的类型
+ if ( _mime ) {
+ ctx.type = _mime
+ }
+
+ // 输出静态资源内容
+ if ( _mime && _mime.indexOf('image/') >= 0 ) {
+ // 如果是图片,则用node原生res,输出二进制数据
+ ctx.res.writeHead(200)
+ ctx.res.write(_content, 'binary')
+ ctx.res.end()
+ } else {
+ // 其他则输出文本
+ ctx.body = _content
+ }
+})
+
+app.listen(3000)
+console.log('[demo] static-server is starting at port 3000')
+```
+
+#### util/content.js
+```js
+const path = require('path')
+const fs = require('fs')
+
+// 封装读取目录内容方法
+const dir = require('./dir')
+
+// 封装读取文件内容方法
+const file = require('./file')
+
+
+/**
+ * 获取静态资源内容
+ * @param {object} ctx koa上下文
+ * @param {string} 静态资源目录在本地的绝对路径
+ * @return {string} 请求获取到的本地内容
+ */
+async function content( ctx, fullStaticPath ) {
+
+ // 封装请求资源的完绝对径
+ let reqPath = path.join(fullStaticPath, ctx.url)
+
+ // 判断请求路径是否为存在目录或者文件
+ let exist = fs.existsSync( reqPath )
+
+ // 返回请求内容, 默认为空
+ let content = ''
+
+ if( !exist ) {
+ //如果请求路径不存在,返回404
+ content = '404 Not Found! o(╯□╰)o!'
+ } else {
+ //判断访问地址是文件夹还是文件
+ let stat = fs.statSync( reqPath )
+
+ if( stat.isDirectory() ) {
+ //如果为目录,则渲读取目录内容
+ content = dir( ctx.url, reqPath )
+
+ } else {
+ // 如果请求为文件,则读取文件内容
+ content = await file( reqPath )
+ }
+ }
+
+ return content
+}
+
+module.exports = content
+```
+
+
+#### util/dir.js
+```js
+const url = require('url')
+const fs = require('fs')
+const path = require('path')
+
+// 遍历读取目录内容方法
+const walk = require('./walk')
+
+/**
+ * 封装目录内容
+ * @param {string} url 当前请求的上下文中的url,即ctx.url
+ * @param {string} reqPath 请求静态资源的完整本地路径
+ * @return {string} 返回目录内容,封装成HTML
+ */
+function dir ( url, reqPath ) {
+
+ // 遍历读取当前目录下的文件、子目录
+ let contentList = walk( reqPath )
+
+ let html = ``
+ for ( let [ index, item ] of contentList.entries() ) {
+ html = `${html}- ${item}`
+ }
+ html = `${html}
`
+
+ return html
+}
+
+module.exports = dir
+```
+
+#### util/file.js
+```js
+const fs = require('fs')
+
+/**
+ * 读取文件方法
+ * @param {string} 文件本地的绝对路径
+ * @return {string|binary}
+ */
+function file ( filePath ) {
+
+ let content = fs.readFileSync(filePath, 'binary' )
+ return content
+}
+
+module.exports = file
+```
+
+#### util/walk.js
+```js
+const fs = require('fs')
+const mimes = require('./mimes')
+
+/**
+ * 遍历读取目录内容(子目录,文件名)
+ * @param {string} reqPath 请求资源的绝对路径
+ * @return {array} 目录内容列表
+ */
+function walk( reqPath ){
+
+ let files = fs.readdirSync( reqPath );
+
+ let dirList = [], fileList = [];
+ for( let i=0, len=files.length; i 1 ) ? itemArr[ itemArr.length - 1 ] : "undefined";
+
+ if( typeof mimes[ itemMime ] === "undefined" ) {
+ dirList.push( files[i] );
+ } else {
+ fileList.push( files[i] );
+ }
+ }
+
+
+ let result = dirList.concat( fileList );
+
+ return result;
+};
+
+module.exports = walk;
+```
+
+
+#### util/mime.js
+```js
+let mimes = {
+ 'css': 'text/css',
+ 'less': 'text/css',
+ 'gif': 'image/gif',
+ 'html': 'text/html',
+ 'ico': 'image/x-icon',
+ 'jpeg': 'image/jpeg',
+ 'jpg': 'image/jpeg',
+ 'js': 'text/javascript',
+ 'json': 'application/json',
+ 'pdf': 'application/pdf',
+ 'png': 'image/png',
+ 'svg': 'image/svg+xml',
+ 'swf': 'application/x-shockwave-flash',
+ 'tiff': 'image/tiff',
+ 'txt': 'text/plain',
+ 'wav': 'audio/x-wav',
+ 'wma': 'audio/x-ms-wma',
+ 'wmv': 'video/x-ms-wmv',
+ 'xml': 'text/xml'
+}
+
+module.exports = mimes
+
+```
+
+
+### 运行效果
+
+#### 启动服务
+```sh
+node --harmony index.js
+```
+
+#### 效果
+
+##### 访问[http://localhost:3000](http://localhost:3000)
+![static-server-result](./../images/static-server-result-01.png)
+
+##### 访问[http://localhost:3000/index.html](http://localhost:3000/index.html)
+![static-server-result](./../images/static-server-result-02.png)
+
+##### 访问[http://localhost:3000/js/index.js](http://localhost:3000/js/index.js)
+![static-server-result](./../images/static-server-result-03.png)
+
+
+
+
+
diff --git a/note/template/add.md b/note/template/add.md
new file mode 100644
index 0000000..80a0c0d
--- /dev/null
+++ b/note/template/add.md
@@ -0,0 +1,59 @@
+# koa2加载模板引擎
+
+## 快速开始
+
+### 安装模块
+```sh
+# 安装koa模板使用中间件
+npm install --save koa-views
+
+# 安装ejs模板引擎
+npm install --save ejs
+```
+
+
+### 使用模板引擎
+
+#### 文件目录
+```
+├── package.json
+├── index.js
+└── view
+ └── index.ejs
+```
+
+#### ./index.js文件
+```js
+const Koa = require('koa')
+const views = require('koa-views')
+const path = require('path')
+const app = new Koa()
+
+// 加载模板引擎
+app.use(views(path.join(__dirname, './view'), {
+ extension: 'ejs'
+}))
+
+app.use( async ( ctx ) => {
+ let title = 'hello koa2'
+ await ctx.render('index', {
+ title,
+ })
+})
+
+app.listen(3000)
+```
+
+#### ./view/index.ejs 模板
+```html
+
+
+
+ <%= title %>
+
+
+ <%= title %>
+ EJS Welcome to <%= title %>
+
+
+```
diff --git a/note/template/ejs.md b/note/template/ejs.md
new file mode 100644
index 0000000..ec80210
--- /dev/null
+++ b/note/template/ejs.md
@@ -0,0 +1,4 @@
+# ejs模板引擎
+
+## 具体查看ejs官方文档
+[https://github.com/mde/ejs](https://github.com/mde/ejs)
\ No newline at end of file
diff --git a/note/upload/busboy.md b/note/upload/busboy.md
new file mode 100644
index 0000000..395f3e9
--- /dev/null
+++ b/note/upload/busboy.md
@@ -0,0 +1,62 @@
+# busboy模块
+
+## 快速开始
+
+### 安装
+```js
+npm install --save busboy
+```
+
+### 模块简介
+busboy 模块是用来解析POST请求,node原生req中的文件流。
+
+### 开始使用
+```js
+const inspect = require('util').inspect
+const path = require('path')
+const fs = require('fs')
+const Busboy = require('busboy')
+
+// req 为node原生请求
+const busboy = new Busboy({ headers: req.headers })
+
+// ...
+
+// 监听文件解析事件
+busboy.on('file', function(fieldname, file, filename, encoding, mimetype) {
+ console.log(`File [${fieldname}]: filename: ${filename}`)
+
+
+ // 文件保存到特定路径
+ file.pipe(fs.createWriteStream('./upload'))
+
+ // 开始解析文件流
+ file.on('data', function(data) {
+ console.log(`File [${fieldname}] got ${data.length} bytes`)
+ })
+
+ // 解析文件结束
+ file.on('end', function() {
+ console.log(`File [${fieldname}] Finished`)
+ })
+})
+
+// 监听请求中的字段
+busboy.on('field', function(fieldname, val, fieldnameTruncated, valTruncated) {
+ console.log(`Field [${fieldname}]: value: ${inspect(val)}`)
+})
+
+// 监听结束事件
+busboy.on('finish', function() {
+ console.log('Done parsing form!')
+ res.writeHead(303, { Connection: 'close', Location: '/' })
+ res.end()
+})
+req.pipe(busboy)
+
+```
+
+## 更多模块信息
+更多详细API可以访问npm官方文档 [https://www.npmjs.com/package/busboy](https://www.npmjs.com/package/busboy)
+
+
diff --git a/note/upload/simple.md b/note/upload/simple.md
new file mode 100644
index 0000000..491d83d
--- /dev/null
+++ b/note/upload/simple.md
@@ -0,0 +1,170 @@
+# 上传文件简单实现
+
+## 依赖模块
+
+### 安装依赖
+
+```sh
+npm install --save busboy
+```
+
+- busboy 是用来解析出请求中文件流
+
+## 例子源码
+
+### 封装上传文件到写入服务的方法
+
+```js
+const inspect = require('util').inspect
+const path = require('path')
+const os = require('os')
+const fs = require('fs')
+const Busboy = require('busboy')
+
+/**
+ * 同步创建文件目录
+ * @param {string} dirname 目录绝对地址
+ * @return {boolean} 创建目录结果
+ */
+function mkdirsSync( dirname ) {
+ if (fs.existsSync( dirname )) {
+ return true
+ } else {
+ if (mkdirsSync( path.dirname(dirname)) ) {
+ fs.mkdirSync( dirname )
+ return true
+ }
+ }
+}
+
+/**
+ * 获取上传文件的后缀名
+ * @param {string} fileName 获取上传文件的后缀名
+ * @return {string} 文件后缀名
+ */
+function getSuffixName( fileName ) {
+ let nameList = fileName.split('.')
+ return nameList[nameList.length - 1]
+}
+
+/**
+ * 上传文件
+ * @param {object} ctx koa上下文
+ * @param {object} options 文件上传参数 fileType文件类型, path文件存放路径
+ * @return {promise}
+ */
+function uploadFile( ctx, options) {
+ let req = ctx.req
+ let res = ctx.res
+ let busboy = new Busboy({headers: req.headers})
+
+ // 获取类型
+ let fileType = options.fileType || 'common'
+ let filePath = path.join( options.path, fileType)
+ let mkdirResult = mkdirsSync( filePath )
+
+ return new Promise((resolve, reject) => {
+ console.log('文件上传中...')
+ let result = {
+ success: false
+ }
+
+ // 解析请求文件事件
+ busboy.on('file', function(fieldname, file, filename, encoding, mimetype) {
+ let fileName = Math.random().toString(16).substr(2) + '.' + getSuffixName(filename)
+ let _uploadFilePath = path.join( filePath, fileName )
+ let saveTo = path.join(_uploadFilePath)
+
+ // 文件保存到制定路径
+ file.pipe(fs.createWriteStream(saveTo))
+
+ // 文件写入事件结束
+ file.on('end', function() {
+ result.success = true
+ result.message = '文件上传成功'
+
+ console.log('文件上传成功!')
+ resolve(result)
+ })
+ })
+
+ // 解析结束事件
+ busboy.on('finish', function( ) {
+ console.log('文件上结束')
+ resolve(result)
+ })
+
+ // 解析错误事件
+ busboy.on('error', function(err) {
+ console.log('文件上出错')
+ reject(result)
+ })
+
+ req.pipe(busboy)
+ })
+
+}
+
+
+module.exports = {
+ uploadFile
+}
+```
+
+### 入口文件
+```js
+const Koa = require('koa')
+const path = require('path')
+const app = new Koa()
+// const bodyParser = require('koa-bodyparser')
+
+const { uploadFile } = require('./util/upload')
+
+// app.use(bodyParser())
+
+app.use( async ( ctx ) => {
+
+ if ( ctx.url === '/' && ctx.method === 'GET' ) {
+ // 当GET请求时候返回表单页面
+ let html = `
+ koa2 upload demo
+
+ `
+ ctx.body = html
+
+ } else if ( ctx.url === '/upload.json' && ctx.method === 'POST' ) {
+ // 上传文件请求处理
+ let result = { success: false }
+ let serverFilePath = path.join( __dirname, 'upload-files' )
+
+ // 上传文件事件
+ result = await uploadFile( ctx, {
+ fileType: 'album', // common or album
+ path: serverFilePath
+ })
+
+ ctx.body = result
+ } else {
+ // 其他请求显示404
+ ctx.body = '404!!! o(╯□╰)o
'
+ }
+})
+
+app.listen(3000)
+console.log('[demo] upload-simple is starting at port 3000')
+```
+
+### 运行结果
+
+![upload-simple-result](./../images/upload-simple-result-01.png)
+
+![upload-simple-result](./../images/upload-simple-result-02.png)
+
+![upload-simple-result](./../images/upload-simple-result-03.png)
+
+![upload-simple-result](./../images/upload-simple-result-04.png)
+