From e034acbb837fd9e270b158a56ec23574a8ab587c Mon Sep 17 00:00:00 2001 From: panmin Date: Thu, 3 Aug 2017 13:54:26 +0800 Subject: [PATCH] koa2-es6 --- README.md | 26 ++ bin/koa2-es6 | 473 +++++++++++++++++++++++ templates3/css/style.css | 8 + templates3/css/style.less | 8 + templates3/css/style.sass | 6 + templates3/css/style.scss | 10 + templates3/css/style.styl | 5 + templates3/ejs/error.ejs | 3 + templates3/ejs/index.ejs | 11 + templates3/hbs/error.hbs | 3 + templates3/hbs/index.hbs | 2 + templates3/hbs/layout.hbs | 10 + templates3/hogan/error.hjs | 3 + templates3/hogan/index.hjs | 11 + templates3/jade/error.pug | 6 + templates3/jade/index.pug | 5 + templates3/jade/layout.pug | 7 + templates3/js/.babelrc | 6 + templates3/js/app.js | 44 +++ templates3/js/common/tools.js | 559 +++++++++++++++++++++++++++ templates3/js/config/config.js | 12 + templates3/js/config/mongo_config.js | 8 + templates3/js/config/redis_config.js | 8 + templates3/js/controller/user.js | 33 ++ templates3/js/db/mongo.js | 24 ++ templates3/js/db/redis.js | 15 + templates3/js/gitignore | 33 ++ templates3/js/index.js | 2 + templates3/js/middlewares/tools.js | 6 + templates3/js/models/user.js | 89 +++++ templates3/js/routes/index.js | 6 + templates3/js/routes/user.js | 18 + templates3/js/www | 92 +++++ templates3/nunjucks/error.nunjucks | 7 + templates3/nunjucks/index.nunjucks | 6 + templates3/nunjucks/layout.nunjucks | 13 + test/koa_es6.cmd | 7 + 37 files changed, 1585 insertions(+) create mode 100644 bin/koa2-es6 create mode 100644 templates3/css/style.css create mode 100644 templates3/css/style.less create mode 100644 templates3/css/style.sass create mode 100644 templates3/css/style.scss create mode 100644 templates3/css/style.styl create mode 100644 templates3/ejs/error.ejs create mode 100644 templates3/ejs/index.ejs create mode 100644 templates3/hbs/error.hbs create mode 100644 templates3/hbs/index.hbs create mode 100644 templates3/hbs/layout.hbs create mode 100644 templates3/hogan/error.hjs create mode 100644 templates3/hogan/index.hjs create mode 100644 templates3/jade/error.pug create mode 100644 templates3/jade/index.pug create mode 100644 templates3/jade/layout.pug create mode 100644 templates3/js/.babelrc create mode 100644 templates3/js/app.js create mode 100644 templates3/js/common/tools.js create mode 100644 templates3/js/config/config.js create mode 100644 templates3/js/config/mongo_config.js create mode 100644 templates3/js/config/redis_config.js create mode 100644 templates3/js/controller/user.js create mode 100644 templates3/js/db/mongo.js create mode 100644 templates3/js/db/redis.js create mode 100644 templates3/js/gitignore create mode 100644 templates3/js/index.js create mode 100644 templates3/js/middlewares/tools.js create mode 100644 templates3/js/models/user.js create mode 100644 templates3/js/routes/index.js create mode 100644 templates3/js/routes/user.js create mode 100644 templates3/js/www create mode 100644 templates3/nunjucks/error.nunjucks create mode 100644 templates3/nunjucks/index.nunjucks create mode 100644 templates3/nunjucks/layout.nunjucks create mode 100644 test/koa_es6.cmd diff --git a/README.md b/README.md index e01ffe8..74376c7 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ http://koajs.com/ - Express-style - Support koa 1.x(supported) - Support koa 2.x(koa middleware supported,need Node.js 7.6+ ,babel optional) +- Support koa 2.x es6 style(koa middleware supported,need Node.js 7.6+ ,babel optional) ## Installation @@ -25,6 +26,30 @@ with 2 commands - koa (Support koa 1.x) - koa2 (Support koa 2.x) +- koa2-es6 (Support koa 2.x es6 style) + +## Quick Start 2.x ES6 +The quickest way to get started with koa is to utilize the executable `koa2(1)` to generate an application as shown below: + +Create the app: + +```bash +$ koa2-es6 /tmp/foo && cd /tmp/foo +``` + +Install dependencies: + +```bash +$ npm install +``` + +Rock and Roll + +```bash +$ npm start +``` + +more detail see [koa2-es6-demo](https://github.com/panmin/koa2-es6-demo) ## Quick Start 1.x @@ -95,6 +120,7 @@ This generator can also be further configured with the following command line fl - tpl = koa 1.x template - tpl_2.x = koa 2.x template + ## License [MIT](LICENSE) diff --git a/bin/koa2-es6 b/bin/koa2-es6 new file mode 100644 index 0000000..fc7e8fd --- /dev/null +++ b/bin/koa2-es6 @@ -0,0 +1,473 @@ +#!/usr/bin/env node + +var program = require('commander'); +var mkdirp = require('mkdirp'); +var os = require('os'); +var fs = require('fs'); +var path = require('path'); +var readline = require('readline'); +var sortedObject = require('sorted-object'); + +var _exit = process.exit; +var eol = os.EOL; +var pkg = require('../package.json'); + +var version = pkg.version; + +// Re-assign process.exit because of commander +// TODO: Switch to a different command framework +process.exit = exit + +// CLI + +before(program, 'outputHelp', function () { + this.allowUnknownOption(); +}); + +program + .version(version) + .usage('[options] [dir]') + .option('-e, --ejs', 'add ejs engine support (defaults to pug/jade)') + .option(' --hbs', 'add handlebars engine support') + .option('-n, --nunjucks', 'add nunjucks engine support') + .option('-H, --hogan', 'add hogan.js engine support') + .option('-c, --css ', 'add stylesheet support (less|stylus|compass|sass) (defaults to plain css)') + .option(' --git', 'add .gitignore') + .option('-f, --force', 'force on non-empty directory') + .parse(process.argv); + +if (!exit.exited) { + main(); +} + +/** + * Install a before function; AOP. + */ + +function before(obj, method, fn) { + var old = obj[method]; + + obj[method] = function () { + fn.call(this); + old.apply(this, arguments); + }; +} + +/** + * Prompt for confirmation on STDOUT/STDIN + */ + +function confirm(msg, callback) { + var rl = readline.createInterface({ + input: process.stdin, + output: process.stdout + }); + + rl.question(msg, function (input) { + rl.close(); + callback(/^y|yes|ok|true$/i.test(input)); + }); +} + +/** + * Create application at the given directory `path`. + * + * @param {String} path + */ + +function createApplication(app_name, path) { + var wait = 5; + + console.log(); + function complete() { + if (--wait) return; + var prompt = launchedFromCmd() ? '>' : '$'; + + console.log(); + console.log(' install dependencies:'); + console.log(' %s cd %s && npm install', prompt, path); + console.log(); + console.log(' run the app:'); + + if (launchedFromCmd()) { + console.log(' %s SET DEBUG=koa* & npm start', prompt, app_name); + } else { + console.log(' %s DEBUG=%s:* npm start', prompt, app_name); + } + + console.log(); + } + + // JavaScript + var babelrc = loadTemplate('js/.babelrc'); + var app = loadTemplate('js/app.js'); + var www = loadTemplate('js/www'); + var index_home = loadTemplate('js/index.js'); + + var tools_common = loadTemplate('js/common/tools.js'); + + var config = loadTemplate('js/config/config.js'); + var mongo_config = loadTemplate('js/config/mongo_config.js'); + var redis_config = loadTemplate('js/config/redis_config.js'); + + var userCtrl = loadTemplate('js/controller/user.js'); + + var mongo_db = loadTemplate('js/db/mongo.js'); + var redis_db = loadTemplate('js/db/redis.js'); + + var tools_mid = loadTemplate('js/middlewares/tools.js'); + + var user_model = loadTemplate('js/models/user.js'); + + var index = loadTemplate('js/routes/index.js'); + var users = loadTemplate('js/routes/user.js'); + + // CSS + var css = loadTemplate('css/style.css'); + var less = loadTemplate('css/style.less'); + var stylus = loadTemplate('css/style.styl'); + var compass = loadTemplate('css/style.scss'); + var sass = loadTemplate('css/style.sass'); + + mkdir(path, function(){ + mkdir(path + '/public'); + mkdir(path + '/public/javascripts'); + mkdir(path + '/public/images'); + mkdir(path + '/public/stylesheets', function(){ + switch (program.css) { + case 'less': + write(path + '/public/stylesheets/style.less', less); + break; + case 'stylus': + write(path + '/public/stylesheets/style.styl', stylus); + break; + case 'compass': + write(path + '/public/stylesheets/style.scss', compass); + break; + case 'sass': + write(path + '/public/stylesheets/style.sass', sass); + break; + default: + write(path + '/public/stylesheets/style.css', css); + } + complete(); + }); + + mkdir(path + '/common', function(){ + write(path + '/common/tools.js', tools_common); + complete(); + }); + + mkdir(path + '/config', function(){ + write(path + '/config/config.js', config); + write(path + '/config/mongo_config.js', mongo_config); + write(path + '/config/redis_config.js', redis_config); + complete(); + }); + + mkdir(path + '/controller', function(){ + write(path + '/controller/user.js', userCtrl); + complete(); + }); + + mkdir(path + '/db', function(){ + write(path + '/db/mongo.js', mongo_db); + write(path + '/db/redis.js', redis_db); + complete(); + }); + + + mkdir(path + '/middlewares', function(){ + write(path + '/middlewares/tools.js', tools_mid); + complete(); + }); + + mkdir(path + '/models', function(){ + write(path + '/models/user.js', user_model); + complete(); + }); + + mkdir(path + '/routes', function(){ + write(path + '/routes/index.js', index); + write(path + '/routes/user.js', users); + complete(); + }); + + mkdir(path + '/views', function(){ + switch (program.template) { + case 'ejs': + copy_template('ejs/index.ejs', path + '/views/index.ejs'); + copy_template('ejs/error.ejs', path + '/views/error.ejs'); + break; + case 'nunjucks': + copy_template('nunjucks/index.nunjucks', path + '/views/index.nunjucks'); + copy_template('nunjucks/layout.nunjucks', path + '/views/layout.nunjucks'); + copy_template('nunjucks/error.nunjucks', path + '/views/error.nunjucks'); + break; + case 'jade': + case 'pug': + copy_template('jade/index.pug', path + '/views/index.pug'); + copy_template('jade/layout.pug', path + '/views/layout.pug'); + copy_template('jade/error.pug', path + '/views/error.pug'); + break; + case 'hjs': + copy_template('hogan/index.hjs', path + '/views/index.hjs'); + copy_template('hogan/error.hjs', path + '/views/error.hjs'); + break; + case 'hbs': + copy_template('hbs/index.hbs', path + '/views/index.hbs'); + copy_template('hbs/layout.hbs', path + '/views/layout.hbs'); + copy_template('hbs/error.hbs', path + '/views/error.hbs'); + break; + } + complete(); + }); + + // CSS Engine support + switch (program.css) { + case 'less': + app = app.replace('{css}', eol + 'app.use(require(\'less-middleware\')(path.join(__dirname, \'public\')));'); + break; + case 'stylus': + app = app.replace('{css}', eol + 'app.use(require(\'stylus\').middleware(path.join(__dirname, \'public\')));'); + break; + case 'compass': + app = app.replace('{css}', eol + 'app.use(require(\'node-compass\')({mode: \'expanded\'}));'); + break; + case 'sass': + app = app.replace('{css}', eol + 'app.use(require(\'node-sass-middleware\')({\n src: path.join(__dirname, \'public\'),\n dest: path.join(__dirname, \'public\'),\n indentedSyntax: true,\n sourceMap: true\n}));'); + break; + default: + app = app.replace('{css}', ''); + } + + // package.json + var pkg = { + name: app_name + , version: '0.1.0' + , private: true + , "scripts": { + "start": "babel-node index.js", + "dev": "cross-env NODE_ENV=development supervisor --harmony index.js", + "pmStart": "cross-env NODE_ENV=production pm2 start index.js --node-args='--harmony' --name '"+app_name+"'", + "pmStop": "cross-env NODE_ENV=production pm2 stop index.js --name '"+app_name+"'", + "pmRestart": "cross-env NODE_ENV=production pm2 restart index.js --node-args='--harmony' --name '"+app_name+"'", + "test": "echo \"Error: no test specified\" && exit 1" + } + , "dependencies": { + "babel": "^6.23.0", + "babel-cli": "^6.24.1", + "babel-core": "^6.25.0", + "babel-preset-es2015": "^6.24.1", + "babel-preset-stage-3": "^6.22.0", + "babel-register": "^6.24.0", + "cross-env": "^5.0.1", + "debug": "^2.6.3", + "koa": "^2.3.0", + "koa-bodyparser": "^4.2.0", + "koa-json": "^2.0.2", + "koa-logger": "^2.0.1", + "koa-onerror": "^1.2.1", + "koa-router": "^7.2.1", + "koa-static": "^3.0.0", + "koa-views": "^5.2.1", + "mongoose": "^4.11.5", + "redis": "^2.7.1" + } + , "devDependencies": { + "babel-plugin-transform-async-to-generator": "^6.24.1", + "babel-plugin-transform-es2015-classes": "^6.24.1", + "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", + "babel-plugin-transform-export-extensions": "^6.22.0" + } + } + + // Template support + switch (program.template) { + case 'jade': + case 'pug': + pkg.dependencies['pug'] = '^2.0.0-rc.1'; + break; + case 'ejs': + pkg.dependencies['ejs'] = '~2.3.3'; + break; + case 'nunjucks': + pkg.dependencies['nunjucks'] = '~3.0.0 '; + break; + case 'hjs': + pkg.dependencies['hjs'] = '~0.0.6'; + break; + case 'hbs': + pkg.dependencies['handlebars'] = '~4.0.5'; + app = app.replace('\'{views}\'', '\'{views}\',\n map: { hbs: \'handlebars\' }'); + break; + default: + } + + app = app.replace('{views}', program.template); + + // CSS Engine support + switch (program.css) { + case 'less': + pkg.dependencies['less-middleware'] = '1.0.x'; + break; + case 'compass': + pkg.dependencies['node-compass'] = '0.2.3'; + break; + case 'stylus': + pkg.dependencies['stylus'] = '0.42.3'; + break; + case 'sass': + pkg.dependencies['node-sass-middleware'] = '0.8.0'; + break; + default: + } + + // sort dependencies like npm(1) + pkg.dependencies = sortedObject(pkg.dependencies); + + // write files + write(path + '/package.json', JSON.stringify(pkg, null, 2)); + write(path + '/.babelrc', babelrc); + write(path + '/index.js', index_home); + write(path + '/app.js', app); + mkdir(path + '/bin', function(){ + www = www.replace('{name}', app_name); + write(path + '/bin/www', www, 0755); + + complete(); + }); + + if (program.git) { + write(path + '/.gitignore', fs.readFileSync(__dirname + '/../templates3/js/gitignore', 'utf-8')); + } + + complete(); + }); +} + +function copy_template(from, to) { + from = path.join(__dirname, '..', 'templates3', from); + write(to, fs.readFileSync(from, 'utf-8')); +} + +/** + * Check if the given directory `path` is empty. + * + * @param {String} path + * @param {Function} fn + */ + +function emptyDirectory(path, fn) { + fs.readdir(path, function(err, files){ + if (err && 'ENOENT' != err.code) throw err; + fn(!files || !files.length); + }); +} + +/** + * Graceful exit for async STDIO + */ + +function exit(code) { + // flush output for Node.js Windows pipe bug + // https://github.com/joyent/node/issues/6247 is just one bug example + // https://github.com/visionmedia/mocha/issues/333 has a good discussion + function done() { + if (!(draining--)) _exit(code); + } + + var draining = 0; + var streams = [process.stdout, process.stderr]; + + exit.exited = true; + + streams.forEach(function(stream){ + // submit empty write request and wait for completion + draining += 1; + stream.write('', done); + }); + + done(); +} + +/** + * Determine if launched from cmd.exe + */ + +function launchedFromCmd() { + return process.platform === 'win32' + && process.env._ === undefined; +} + +/** + * Load template file. + */ + +function loadTemplate(name) { + return fs.readFileSync(path.join(__dirname, '..', 'templates3', name), 'utf-8'); +} + +/** + * Main program. + */ + +function main() { + // Path + var destinationPath = program.args.shift() || '.'; + + // App name + var appName = path.basename(path.resolve(destinationPath)); + + // Template engine + program.template = 'pug'; + if (program.ejs) program.template = 'ejs'; + if (program.jade) program.template = 'pug'; + if (program.hogan) program.template = 'hjs'; + if (program.hbs) program.template = 'hbs'; + if (program.nunjucks) program.template = 'nunjucks'; + + // Generate application + emptyDirectory(destinationPath, function (empty) { + if (empty || program.force) { + createApplication(appName, destinationPath); + } else { + confirm('destination is not empty, continue? [y/N] ', function (ok) { + if (ok) { + process.stdin.destroy(); + createApplication(appName, destinationPath); + } else { + console.error('aborting'); + exit(1); + } + }); + } + }); +} + +/** + * echo str > path. + * + * @param {String} path + * @param {String} str + */ + +function write(path, str, mode) { + fs.writeFileSync(path, str, { mode: mode || 0666 }); + console.log(' \x1b[36mcreate\x1b[0m : ' + path); +} + +/** + * Mkdir -p. + * + * @param {String} path + * @param {Function} fn + */ + +function mkdir(path, fn) { + mkdirp(path, 0755, function(err){ + if (err) throw err; + console.log(' \033[36mcreate\033[0m : ' + path); + fn && fn(); + }); +} diff --git a/templates3/css/style.css b/templates3/css/style.css new file mode 100644 index 0000000..9453385 --- /dev/null +++ b/templates3/css/style.css @@ -0,0 +1,8 @@ +body { + padding: 50px; + font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; +} + +a { + color: #00B7FF; +} diff --git a/templates3/css/style.less b/templates3/css/style.less new file mode 100644 index 0000000..9453385 --- /dev/null +++ b/templates3/css/style.less @@ -0,0 +1,8 @@ +body { + padding: 50px; + font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; +} + +a { + color: #00B7FF; +} diff --git a/templates3/css/style.sass b/templates3/css/style.sass new file mode 100644 index 0000000..d683e30 --- /dev/null +++ b/templates3/css/style.sass @@ -0,0 +1,6 @@ +body + padding: 50px + font: 14px "Lucida Grande", Helvetica, Arial, sans-serif + +a + color: #00B7FF diff --git a/templates3/css/style.scss b/templates3/css/style.scss new file mode 100644 index 0000000..d5eb9e7 --- /dev/null +++ b/templates3/css/style.scss @@ -0,0 +1,10 @@ +@import "compass/css3"; + +body { + padding: 50px; + font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; +} + +a { + color: #00B7FF; +} diff --git a/templates3/css/style.styl b/templates3/css/style.styl new file mode 100644 index 0000000..98047c5 --- /dev/null +++ b/templates3/css/style.styl @@ -0,0 +1,5 @@ +body + padding: 50px + font: 14px "Lucida Grande", Helvetica, Arial, sans-serif +a + color: #00B7FF diff --git a/templates3/ejs/error.ejs b/templates3/ejs/error.ejs new file mode 100644 index 0000000..7cf94ed --- /dev/null +++ b/templates3/ejs/error.ejs @@ -0,0 +1,3 @@ +

<%= message %>

+

<%= error.status %>

+
<%= error.stack %>
diff --git a/templates3/ejs/index.ejs b/templates3/ejs/index.ejs new file mode 100644 index 0000000..cc50d13 --- /dev/null +++ b/templates3/ejs/index.ejs @@ -0,0 +1,11 @@ + + + + <%= title %> + + + +

<%= title %>

+

EJS Welcome to <%= title %>

+ + diff --git a/templates3/hbs/error.hbs b/templates3/hbs/error.hbs new file mode 100644 index 0000000..0659765 --- /dev/null +++ b/templates3/hbs/error.hbs @@ -0,0 +1,3 @@ +

{{message}}

+

{{error.status}}

+
{{error.stack}}
diff --git a/templates3/hbs/index.hbs b/templates3/hbs/index.hbs new file mode 100644 index 0000000..1f308fd --- /dev/null +++ b/templates3/hbs/index.hbs @@ -0,0 +1,2 @@ +

{{title}}

+

Welcome to {{title}}

diff --git a/templates3/hbs/layout.hbs b/templates3/hbs/layout.hbs new file mode 100644 index 0000000..068eb6b --- /dev/null +++ b/templates3/hbs/layout.hbs @@ -0,0 +1,10 @@ + + + + {{title}} + + + + {{{body}}} + + diff --git a/templates3/hogan/error.hjs b/templates3/hogan/error.hjs new file mode 100644 index 0000000..36a6196 --- /dev/null +++ b/templates3/hogan/error.hjs @@ -0,0 +1,3 @@ +

{{ message }}

+

{{ error.status }}

+
{{ error.stack }}
diff --git a/templates3/hogan/index.hjs b/templates3/hogan/index.hjs new file mode 100644 index 0000000..821a8de --- /dev/null +++ b/templates3/hogan/index.hjs @@ -0,0 +1,11 @@ + + + + {{ title }} + + + +

{{ title }}

+

Welcome to {{ title }}

+ + diff --git a/templates3/jade/error.pug b/templates3/jade/error.pug new file mode 100644 index 0000000..51ec12c --- /dev/null +++ b/templates3/jade/error.pug @@ -0,0 +1,6 @@ +extends layout + +block content + h1= message + h2= error.status + pre #{error.stack} diff --git a/templates3/jade/index.pug b/templates3/jade/index.pug new file mode 100644 index 0000000..3d63b9a --- /dev/null +++ b/templates3/jade/index.pug @@ -0,0 +1,5 @@ +extends layout + +block content + h1= title + p Welcome to #{title} diff --git a/templates3/jade/layout.pug b/templates3/jade/layout.pug new file mode 100644 index 0000000..15af079 --- /dev/null +++ b/templates3/jade/layout.pug @@ -0,0 +1,7 @@ +doctype html +html + head + title= title + link(rel='stylesheet', href='/stylesheets/style.css') + body + block content diff --git a/templates3/js/.babelrc b/templates3/js/.babelrc new file mode 100644 index 0000000..ba3eb74 --- /dev/null +++ b/templates3/js/.babelrc @@ -0,0 +1,6 @@ +{ + "presets": [ + "es2015" + ], + "plugins": [] +} \ No newline at end of file diff --git a/templates3/js/app.js b/templates3/js/app.js new file mode 100644 index 0000000..3ebb4e6 --- /dev/null +++ b/templates3/js/app.js @@ -0,0 +1,44 @@ +import Koa from 'koa' +import views from 'koa-views' +import json from 'koa-json' +import onerror from'koa-onerror' +import bodyparser from 'koa-bodyparser' +import logger from 'koa-logger' +import koaStatic from 'koa-static' +import koaRouter from 'koa-router' + +// 自定义中间件 +import mongo from './db/mongo' +import redis from './db/redis' //启动redis +import tools from './middlewares/tools' +import routes from './routes' + + +// 初始化 +const app = new Koa() +const router = koaRouter() +mongo()//启动mongo + +// error handler +onerror(app) + +// middlewares +app.use(bodyparser({ + enableTypes:['json', 'form', 'text'] +})) +app.use(json()) +app.use(logger()) +app.use(koaStatic(__dirname + '/public')) + +app.use(views(__dirname + '/views', { + extension: '{views}' +})) + + +// routes 加载路由 +router.use('/',tools) +routes(router) +app.use(router.routes()); +app.use(router.allowedMethods()); + +module.exports = app diff --git a/templates3/js/common/tools.js b/templates3/js/common/tools.js new file mode 100644 index 0000000..eb49098 --- /dev/null +++ b/templates3/js/common/tools.js @@ -0,0 +1,559 @@ +class Tools{ + constructor(ctx) { + Object.assign(this, { + ctx + }) + } + + /** + * API接口调用返回JSON格式内容 + * @param {Number} code + * @param {String} message + * @param {Objext} data + */ + setJson(code, message, data) { + return this.ctx.body={ + code: code || 0, + message: message || null, + data: data || null + } + } + + /** + * 返回文件后缀 + * @param {Object} file + * @return {String} + */ + getFilenameExt(file) { + const types = file.name.split('.') + return types[types.length - 1] + } + + /** + * 返回指定范围内的一个整数 + * @param {Number} min + * @param {Number} max + * @return {String} + */ + rand(min, max) { + return Math.floor(Math.random() * (max - min + 1) + min) + } + + /** + * 生成字符串组合 + * @param {Number} size + * @return {String} + */ + randString(size) { + let result = '' + let allChar = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' + + size = size || 1 + + while (size--) { + result += allChar.charAt(this.rand(0, allChar.length - 1)) + } + + return result + } + + /** + * 生成文件名 + * @param {Object} file + * @return {String} + */ + randFilename(file) { + return this.randString(this.rand(10, 100)) + Date.parse(new Date()) + '.' + this.getFilenameExt(file) + } + + /** + * 判断某个元素是否为字符串 + * @param {String} value + * @return {Boolean} + */ + isString(value) { + return typeof value === 'string' + } + + /** + * 判断某个元素是否为函数 + * @param {Function} value + * @return {Boolean} + */ + isFunction(value) { + return this.type(value) === 'function' + } + + /** + * 判断某个元素是否为数组 + * @param {Array} value + * @return {Boolean} + */ + isArray(value) { + return Array.isArray(value) + } + + /** + * 判断某个元素是否为对象 + * @param {Obejct} value + * @return {Boolean} + */ + isObject(value) { + return value !== null && typeof value === 'object' + } + + /** + * 判断某个元素是否为数值 + * @param {Number} value + * @return {Boolean} + */ + isNumber(value) { + return typeof value === 'number' + } + + /** + * 判断某个元素是否为日期 + * @param {Date} value + * @return {Boolean} + */ + isDate(value) { + return this.type(value) === '[object Date]' + } + + /** + * 判断某个元素是否为正则表达式 + * @param {RegExp} value + * @return {Boolean} + */ + isRegExp(value) { + return this.type(value) === '[object RegExp]' + } + + /** + * 判断某个元素是否为File对象 + * @param {Object} obj + * @return {Boolean} + */ + isFile(obj) { + return this.type(obj) === '[object File]' + } + + /** + * 判断某个元素是否为FormData对象 + * @param {Object} obj + * @return {Boolean} + */ + isFormData(obj) { + return this.type(obj) === '[object FormData]' + } + + /** + * 判断某个元素是否为Blob对象 + * @param {Object} obj + * @return {Boolean} + */ + isBlob(obj) { + return this.type(obj) === '[object Blob]' + } + + /** + * 判断某个元素是否为布尔值 + * @param {boolean} value + * @return {Boolean} + */ + isBoolean(value) { + return typeof value === 'boolean' + } + + /** + * 判断某个元素是否为Promise对象 + * @param {Function} obj + * @return {Boolean} + */ + isPromiseLike(obj) { + return obj && this.isFunction(obj.then) + } + + /** + * 判断数组类型 + * @param {Array} value + * @return {Boolean} + */ + isTypedArray(value) { + const TYPED_ARRAY_REGEXP = /^\[object (?:Uint8|Uint8Clamped|Uint16|Uint32|Int8|Int16|Int32|Float32|Float64)Array\]$/ + return value && this.isNumber(value.length) && TYPED_ARRAY_REGEXP.test(this.type(value)) + } + + /** + * 判断某个元素是否为ArrayBuffer对象 + * @param {Object} obj + * @return {Boolean} + */ + isArrayBuffer(obj) { + return this.type(obj) === '[object ArrayBuffer]' + } + + /** + * 判断某个元素是否为defined + * @param {undefined} value + * @return {Boolean} + */ + isDefined(value) { + return typeof value !== 'undefined' + } + + /** + * 判断某个元素是否为undefined + * @param {undefined} value + * @return {Boolean} + */ + isUndefined(value) { + return typeof value === 'undefined' + } + + /** + * 判断某个元素是否为null + * @param {Null} value + * @return {Boolean} + */ + isNull(value) { + return value === null + } + + /** + * 判断某个元素是否为有限数 + * @param {Number} value + * @return {Boolean} + */ + isFinite(value) { + return typeof value == 'number' && isFinite(value) + } + + /** + * 判断某个元素是否为自然数 + * @param {Number} value + * @return {Boolean} + */ + isNaN(value) { + return this.isNumber(value) && value != +value + } + + isError(value) { + return this.type(value) === '[object Error]' + } + + /** + * 删除字符串左右两端的空格 + * @param {String} str + * @return {String} + */ + trim(str) { + return this.isString(str) ? str.trim() : str + } + + /** + * 字符串转义 + * @param {String} str + * @return {String} + */ + escapeForRegexp(str) { + return str.replace(/([-()\[\]{}+?*.$\^|,:#= 0) { + array.splice(index, 1) + } + return index + } + + /** + * 日期增加分钟 + * @param {Date} date + * @param {Number} minutes + * @return {Date} + */ + addDateMinutes(date, minutes) { + date = new Date(date.getTime()) + date.setMinutes(date.getMinutes() + minutes || 0) + return date + } + + /** + * 对象解析出JSON字符串 + * @param {Object} obj + * @param {Number} pretty + * @return {Object} + */ + toJson(obj, pretty) { + if (this.isUndefined(obj)) return undefined + if (!this.isNumber(pretty)) { + pretty = pretty ? 2 : null + } + return JSON.stringify(obj, null, pretty) + } + + /** + * JSON字符串解析成对象 + * @param {String} json + * @return {Object} + */ + fromJson(json) { + return this.isString(json) ? JSON.parse(json) : json + } + + /** + * 扩展对象 + * @return {Object} + */ + extend() { + let src, copyIsArray, copy, name, options, clone, + target = arguments[0] || {}, + i = 1, + length = arguments.length, + deep = !1; + + if (typeof target === 'boolean') { + deep = target + target = arguments[ i ] || {} + i++ + } + + if (typeof target !== 'object' && !this.isFunction(target)) { + target = {} + } + + if (i === length) { + target = this + i-- + } + + for (; i < length; i++) { + if ( (options = arguments[ i ]) != null ) { + for (name in options) { + src = target[name] + copy = options[name] + + if (target === copy) { + continue + } + + if (deep && copy && (this.isPlainObject(copy) || (copyIsArray = this.isArray(copy)))) { + if (copyIsArray) { + copyIsArray = !1 + clone = src && this.isArray(src) ? src : [] + } else { + clone = src && this.isPlainObject(src) ? src : {} + } + target[name] = this.extend(deep, clone, copy) + } else if (copy !== undefined) { + target[name] = copy + } + } + } + } + return target + } + + /** + * 判断传入的参数是否为纯粹的对象,即直接量{}或new Object()创建的对象 + * @param {[type]} obj [description] + * @return {Boolean} [description] + */ + isPlainObject(obj) { + let getProto = Object.getPrototypeOf + let class2type = {} + let toString = class2type.toString + let hasOwn = class2type.hasOwnProperty + let fnToString = hasOwn.toString + let ObjectFunctionString = fnToString.call(Object) + let proto, Ctor + if (!obj || this.type(obj) !== '[object Object]') { + return !1 + } + proto = getProto( obj ) + if ( !proto ) { + return !0 + } + Ctor = hasOwn.call(proto, 'constructor') && proto.constructor + return typeof Ctor === 'function' && fnToString.call(Ctor) === ObjectFunctionString + } + + /** + * 判断对象是否为空 + * @param {Object} obj + * @return {Boolean} + */ + isEmptyObject(obj) { + for (let i in obj) + return !1 + return !0 + } + + /** + * 判断对象的类型 + * @param {Object} obj + * @return {String} + */ + type(obj) { + const toString = Object.prototype.toString + + if ( obj == null ) { + return obj + '' + } + + return typeof obj === 'object' || typeof obj === 'function' ? toString.call(obj) || 'object' : typeof obj + } + + /** + * 合并对象并返回一个新的对象,目标对象自身也会改变 + * @param {Array} args + * @return {Object} + */ + merge(...args) { + return Object.assign(...args) + } + + /** + * 拷贝对象并返回一个新的对象 + * @param {Object} obj + * @return {Object} + */ + clone(obj) { + if (typeof obj !== 'object' || !obj) { + return obj + } + let copy = {} + for (let attr in obj) { + if (obj.hasOwnProperty(attr)) { + copy[attr] = obj[attr] + } + } + return copy + } + + /** + * 删除对象上的指定属性并返回一个新的对象 + * @param {Object} obj + * @param {Array} keys + * @return {[type]} + */ + omit(obj, keys) { + let o = this.clone(obj) + keys.forEach(key => { + delete o[key] + }) + return o + } + + /** + * 返回一个新数组,数组中的元素为指定属性的值 + * @param {Array} arr + * @param {String} key + * @return {Array} + */ + pluck(arr, key) { + if (typeof arr !== 'object' || arr.length === 0) { + return [] + } + if (!key) { + return arr + } + return arr.map(a => a[key]) + } + + /** + * 返回序列化的值 + * @param {String} value + * @return {String} + */ + serializeValue(value) { + if (this.isObject(value)) return this.isDate(value) ? value.toISOString() : this.toJson(value) + return value + } + + /** + * 编码URI + * @param {String} value + * @param {String} pctEncodeSpaces + * @return {String} + */ + encodeUriQuery(value, pctEncodeSpaces) { + return encodeURIComponent(value) + .replace(/%40/gi, '@') + .replace(/%3A/gi, ':') + .replace(/%24/g, '$') + .replace(/%2C/gi, ',') + .replace(/%3B/gi, ';') + .replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')) + } + + /** + * 对象序列化 + * @param {Object} obj + * @return {String} + */ + paramSerializer(obj) { + if (!obj) return '' + let that = this + let parts = [] + for(let key in obj) { + const value = obj[key] + if (value === null || that.isUndefined(value)) return + if (that.isArray(value)) { + value.forEach(function(v) { + parts.push(that.encodeUriQuery(key) + '=' + that.encodeUriQuery(that.serializeValue(v))) + }) + } else { + parts.push(that.encodeUriQuery(key) + '=' + that.encodeUriQuery(that.serializeValue(value))) + } + } + return parts.join('&') + } + + /** + * 拼接URL + * @param {String} obj + * @param {Object} obj + * @return {String} + */ + buildUrl(url, obj) { + const serializedParams = this.paramSerializer(obj) + if (serializedParams.length > 0) { + url += ((url.indexOf('?') == -1) ? '?' : '&') + serializedParams + } + return url + } +} + +export default Tools \ No newline at end of file diff --git a/templates3/js/config/config.js b/templates3/js/config/config.js new file mode 100644 index 0000000..fd38d26 --- /dev/null +++ b/templates3/js/config/config.js @@ -0,0 +1,12 @@ +export default { + port:8080, + sercet:'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', + upload: { + tmp : 'public/tmp/', + path: 'public/uploads/' + }, + logs:{ + success:'logs/success.log', + error:'logs/error.log' + } +} \ No newline at end of file diff --git a/templates3/js/config/mongo_config.js b/templates3/js/config/mongo_config.js new file mode 100644 index 0000000..ccfc1ee --- /dev/null +++ b/templates3/js/config/mongo_config.js @@ -0,0 +1,8 @@ +export default { + development: { + connectionString: 'mongodb://127.0.0.1:27017/test' + }, + production: { + connectionString: 'mongodb://127.0.0.1:27017/test' + } +} \ No newline at end of file diff --git a/templates3/js/config/redis_config.js b/templates3/js/config/redis_config.js new file mode 100644 index 0000000..3765edc --- /dev/null +++ b/templates3/js/config/redis_config.js @@ -0,0 +1,8 @@ +export default { + development: { + connectionString: 'redis://127.0.0.1:6379' + }, + production: { + connectionString: 'redis://127.0.0.1:6379', + } +} \ No newline at end of file diff --git a/templates3/js/controller/user.js b/templates3/js/controller/user.js new file mode 100644 index 0000000..4bf27ad --- /dev/null +++ b/templates3/js/controller/user.js @@ -0,0 +1,33 @@ +import userModel from '../models/user' + + +class Ctrl{ + constructor(){ + Object.assign(this,{ + model:userModel + }) + this.login = this.login.bind(this) + console.log('------------') + } + + async login(ctx,next){ + console.log(ctx.request.body) + const username = ctx.request.body.username + const pwd = ctx.request.body.pwd + try{ + const user = await this.model.findOne({username:username}) + if(!user){ + console.log('no user') + return ctx.tools.setJson(1,'帐号或者密码不存在') + } + if(user.password==pwd){ + return ctx.tools.setJson(0,'登录成功',user) + } + }catch(err){ + console.log(err) + next(err) + } + } +} + +export default new Ctrl() \ No newline at end of file diff --git a/templates3/js/db/mongo.js b/templates3/js/db/mongo.js new file mode 100644 index 0000000..85cfe58 --- /dev/null +++ b/templates3/js/db/mongo.js @@ -0,0 +1,24 @@ +import mongoose from 'mongoose' +import mongo_config from '../config/mongo_config' +const mongo = () => { + mongoose.Promise = Promise; + const dblink = mongo_config[process.env.NODE_ENV || 'development'].connectionString + + const opts = { + useMongoClient:true + } + + mongoose.connect(dblink, opts) + const db = mongoose.connection + db.on('error', err => { + console.log('------ Mongodb connection failed ------' + err) + mongoose.disconnect() + }) + db.on('open', () => console.log('------ Mongodb connection succeed ------')) + db.on('close',() => { + console.log('------ Mongodb connection colsed,connect again ------') + mongoose.connect(dblink, opts) + }) +} + +export default mongo \ No newline at end of file diff --git a/templates3/js/db/redis.js b/templates3/js/db/redis.js new file mode 100644 index 0000000..4456f7b --- /dev/null +++ b/templates3/js/db/redis.js @@ -0,0 +1,15 @@ +import redis from 'redis' +import config from '../config/redis_config' + +// const redisLink = config['development']['connectionString'] +const redisLink = config[process.env.NODE_ENV || 'development']['connectionString'] +const redisClient = redis.createClient(redisLink) + +redisClient + .on('error', err => console.log('------ Redis connection failed ------' + err)) + .on('connect', () => console.log('------ Redis connection succeed ------')) + +export default { + redis: redis, + redisClient: redisClient, +} \ No newline at end of file diff --git a/templates3/js/gitignore b/templates3/js/gitignore new file mode 100644 index 0000000..e97c3f6 --- /dev/null +++ b/templates3/js/gitignore @@ -0,0 +1,33 @@ +# Logs +logs +*.log + +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directory +# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- +node_modules + +# Debug log from npm +npm-debug.log + +# for macos +.DS_Store diff --git a/templates3/js/index.js b/templates3/js/index.js new file mode 100644 index 0000000..726ee45 --- /dev/null +++ b/templates3/js/index.js @@ -0,0 +1,2 @@ +require('babel-core/register'); +require('./bin/www'); \ No newline at end of file diff --git a/templates3/js/middlewares/tools.js b/templates3/js/middlewares/tools.js new file mode 100644 index 0000000..cd26e31 --- /dev/null +++ b/templates3/js/middlewares/tools.js @@ -0,0 +1,6 @@ +import tools from '../common/tools' + +export default async (ctx, next) => { + ctx.tools = new tools(ctx) + await next() +} \ No newline at end of file diff --git a/templates3/js/models/user.js b/templates3/js/models/user.js new file mode 100644 index 0000000..0b7c247 --- /dev/null +++ b/templates3/js/models/user.js @@ -0,0 +1,89 @@ +import mongoose from 'mongoose' +import crypto from 'crypto' + +const MAX_LOGIN_ATTEMPTS = 5 +const LOCK_TIME = 2 * 60 * 60 * 1000 + +const Schema = mongoose.Schema({ + username : String, + password : String, + avatar : String, + tel : Number, + email : String, + nickname : String, + gender : String, + birthday : Date, + loginAttempts: { + type : Number, + required: true, + default : 0, + }, + lockUntil: { + type: Number, + }, + create_at: { + type : Date, + default: Date.now(), + }, + update_at: Date, +}) + +const reasons = Schema.statics.failedLogin = { + NOT_FOUND : 0, + PASSWORD_INCORRECT: 1, + MAX_ATTEMPTS : 2, +} + +Schema.virtual('isLocked').get(function() { + return !!(this.lockUntil && this.lockUntil > Date.now()) +}) + +Schema.methods.comparePassword = function(candidatePassword) { + return crypto.createHash('md5').update(candidatePassword).digest('hex') === this.password +} + +Schema.methods.incLoginAttempts = function() { + // if we have a previous lock that has expired, restart at 1 + if (this.lockUntil && this.lockUntil < Date.now()) { + return this.updateAsync({ + $set: { loginAttempts: 1 }, + $unset: { lockUntil: 1 } + }) + } + // otherwise we're incrementing + const updates = { $inc: { loginAttempts: 1 } } + // lock the account if we've reached max attempts and it's not locked already + if (this.loginAttempts + 1 >= MAX_LOGIN_ATTEMPTS && !this.isLocked) { + updates.$set = { lockUntil: Date.now() + LOCK_TIME } + } + return this.updateAsync(updates) +} + +Schema.statics.getAuthenticated = function(username, password) { + return this.findOneAsync({username: username}) + .then(doc => { + // make sure the user exists + if (!doc) { + return reasons.NOT_FOUND + } + // check if the account is currently locked + if (doc.isLocked) { + return doc.incLoginAttempts().then(() => reasons.MAX_ATTEMPTS) + } + // test for a matching password + if (doc.comparePassword(password)) { + // if there's no lock or failed attempts, just return the doc + if (!doc.loginAttempts && !doc.lockUntil) return doc + // reset attempts and lock info + const updates = { + $set: { loginAttempts: 0 }, + $unset: { lockUntil: 1 } + } + return doc.updateAsync(updates).then(() => doc) + } + // password is incorrect, so increment login attempts before responding + return doc.incLoginAttempts().then(() => reasons.PASSWORD_INCORRECT) + }) +} + +export default mongoose.model('user', Schema) \ No newline at end of file diff --git a/templates3/js/routes/index.js b/templates3/js/routes/index.js new file mode 100644 index 0000000..f896110 --- /dev/null +++ b/templates3/js/routes/index.js @@ -0,0 +1,6 @@ +import users from './user' +import User from '../controller/user' + +export default router => { + new users(router) +} diff --git a/templates3/js/routes/user.js b/templates3/js/routes/user.js new file mode 100644 index 0000000..d38e7d6 --- /dev/null +++ b/templates3/js/routes/user.js @@ -0,0 +1,18 @@ +import User from '../controller/user' + +class Router{ + constructor(router){ + Object.assign(this, { + router + }) + this.init() + } + + //初始化路由 + init(){ + console.log('/login') + this.router.post('/login',User.login) + } +} + +export default Router diff --git a/templates3/js/www b/templates3/js/www new file mode 100644 index 0000000..167cb54 --- /dev/null +++ b/templates3/js/www @@ -0,0 +1,92 @@ +#!/usr/bin/env node + +/** + * Module dependencies. + */ + +import app from '../app' +import debug from 'debug' +import http from 'http' + +const debugServer = debug('demo:server') + +/** + * Get port from environment and store in Express. + */ + +const port = normalizePort(process.env.PORT || '3000'); +// app.set('port', port); + +/** + * Create HTTP server. + */ + +const server = http.createServer(app.callback()); + +/** + * Listen on provided port, on all network interfaces. + */ + +server.listen(port); +server.on('error', onError); +server.on('listening', onListening); + +/** + * Normalize a port into a number, string, or false. + */ + +function normalizePort(val) { + const port = parseInt(val, 10); + + if (isNaN(port)) { + // named pipe + return val; + } + + if (port >= 0) { + // port number + return port; + } + + return false; +} + +/** + * Event listener for HTTP server "error" event. + */ + +function onError(error) { + if (error.syscall !== 'listen') { + throw error; + } + + const bind = typeof port === 'string' + ? 'Pipe ' + port + : 'Port ' + port; + + // handle specific listen errors with friendly messages + switch (error.code) { + case 'EACCES': + console.error(bind + ' requires elevated privileges'); + process.exit(1); + break; + case 'EADDRINUSE': + console.error(bind + ' is already in use'); + process.exit(1); + break; + default: + throw error; + } +} + +/** + * Event listener for HTTP server "listening" event. + */ + +function onListening() { + const addr = server.address(); + const bind = typeof addr === 'string' + ? 'pipe ' + addr + : 'port ' + addr.port; + debugServer('Listening on ' + bind); +} diff --git a/templates3/nunjucks/error.nunjucks b/templates3/nunjucks/error.nunjucks new file mode 100644 index 0000000..3385594 --- /dev/null +++ b/templates3/nunjucks/error.nunjucks @@ -0,0 +1,7 @@ +{% extends "views/layout.nunjucks" %} + +{% block content %} +

{{ message }}

+

{{ error.status }}

+
 {{ error.stack }} 
+{% endblock %} \ No newline at end of file diff --git a/templates3/nunjucks/index.nunjucks b/templates3/nunjucks/index.nunjucks new file mode 100644 index 0000000..66a3f9f --- /dev/null +++ b/templates3/nunjucks/index.nunjucks @@ -0,0 +1,6 @@ +{% extends 'views/layout.nunjucks' %} + +{% block content %} +

{{ title }}

+

Welcome to {{ title }}

+{% endblock %} \ No newline at end of file diff --git a/templates3/nunjucks/layout.nunjucks b/templates3/nunjucks/layout.nunjucks new file mode 100644 index 0000000..ed35ac9 --- /dev/null +++ b/templates3/nunjucks/layout.nunjucks @@ -0,0 +1,13 @@ + + + + + {{ title }} + + + + {% block content %} + + {% endblock %} + + \ No newline at end of file diff --git a/test/koa_es6.cmd b/test/koa_es6.cmd new file mode 100644 index 0000000..49af563 --- /dev/null +++ b/test/koa_es6.cmd @@ -0,0 +1,7 @@ +@IF EXIST "%~dp0\node.exe" ( + "%~dp0\node.exe" "%~dp0\..\bin\koa2-es6" %* +) ELSE ( + @SETLOCAL + @SET PATHEXT=%PATHEXT:;.JS;=;% + node "%~dp0\..\bin\koa2-es6" %* +) \ No newline at end of file