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 ( + + + + +
+

error

+
+
+ +
+ ) + } +} + + +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 + +
+

index

+
+
+ +
+ ) + } +} + + +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 ( + + +
+ + + + nav 1 + + + + nav 2 + + + + nav 3 + + + + +
+ +
+ + 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 ( +
+ Hello World ©2017 Created by hello world +
+ ) + } +} + + +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 ( +
+
+ + Home + Admin + Work + +
+ ) + } +} + + +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 ( +
+
+ + {getFieldDecorator('userName', { + rules: [{ required: true, message: '请您输入账号名称!' }], + })( + } placeholder="请您输入用户名称!" /> + )} + + + {getFieldDecorator('password', { + rules: [{ required: true, message: '请您输入账号密码!' }], + })( + } type="password" placeholder="请您输入账号密码" /> + )} + + + {getFieldDecorator('remember', { + valuePropName: 'checked', + initialValue: true, + })( + 记住登录 + )} + 忘记密码
+ +
+
+
+ ); + }, +})) +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 ( +
+ + 用户名 + + + + + )} + hasFeedback + > + {getFieldDecorator('userName', { + rules: [{ required: true, message: '请输入您的用户名' }], + })( + + )} + + + {getFieldDecorator('email', { + rules: [{ + type: 'email', message: '请您输入正确格式的邮箱地址', + }, { + required: true, message: '请您输入邮箱地址!', + }], + })( + + )} + + + {getFieldDecorator('password', { + rules: [{ + required: true, message: '请您输入您的账号密码!', + }, { + validator: this.checkConfirm, + }], + })( + + )} + + + {getFieldDecorator('confirmPassword', { + rules: [{ + required: true, message: '请您再次输入账号密码进行确认!', + }, { + validator: this.checkPassword, + }], + })( + + )} + + + + {getFieldDecorator('agreement', { + valuePropName: 'checked', + })( + 我已阅读 《xxxx协议》 + )} + + + + +
+ ) + }, +})) + + +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

+
+

userName

+
+

nickName

+
+

email

+
+ +
+ ` + 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

+
+

userName

+
+

nickName

+
+

email

+
+ +
+ ` + 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

+
+

file upload

+

+ +
+ ` + 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 + +
+

index

+
+
+ +
+ ) + } +} +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

+
+

userName

+
+

nickName

+
+

email

+
+ +
+ ` + 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

+
+

userName

+
+

nickName

+
+

email

+
+ +
+ ` + 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

+
+

file upload

+

+ +
+ ` + 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) +