This commit is contained in:
2017-01-23 03:38:59 +08:00
parent 878d9b63f6
commit f5dfeaa123
18 changed files with 403 additions and 70 deletions

View File

@ -1,4 +1,4 @@
{
"presets": ["es2015", "stage-2"],
"plugins": ["transform-runtime"]
'presets': ['es2015', 'stage-2'],
'plugins': ['transform-runtime']
}

View File

@ -7,14 +7,14 @@ module.exports = {
extends: 'standard',
plugins: [
'html',
"promise"
'promise'
],
env: {
"node": true
'node': true
},
rules: { // add your custom rules here
// allow console
"no-console": 0,
'no-console': 0,
// allow paren-less arrow functions
'arrow-parens': 0,
// allow async-await

156
README.md
View File

@ -8,7 +8,7 @@
此脚手架只安装了一些和Koa2不冲突的搭建RESTful API Server的必要插件附带每一个插件的说明。采用ESlint进行语法检查。
因此脚手架主要提供RESTful API故暂时不考虑前端静态资源处理。基本目录结构与vue-cli保持一致可配合React、AngularJS、Vue.js等前端框架使用。在Cordova/PhoneGap中使用时需要开启跨域功能。
因此脚手架主要提供RESTful API故暂时不考虑前端静态资源处理,只提供静态资源访问的基本方法便于访问用户上传到服务器的图片等资源。基本目录结构与vue-cli保持一致可配合React、AngularJS、Vue.js等前端框架使用。在Cordova/PhoneGap中使用时需要开启跨域功能。
**免责声明:** 此脚手架仅为方便开发提供基础环境,任何人或组织均可随意克隆使用,使用引入的框架需遵循原作者规定的相关协议(框架列表及来源地址在下方)。采用此脚手架产生的任何后果请自行承担,本人不对此脚手架负任何法律责任,使用即代表同意此条。
@ -22,7 +22,7 @@ $ git clone https://github.com/yi-ge/koa2-API-scaffold.git
$ cd mv koa2-API-scaffold
$ npm install
$ npm run dev #可执行npm start跳过ESlint检查。
$ npm run dev # 可执行npm start跳过ESlint检查。
```
访问: http://127.0.0.1:3000/
@ -37,7 +37,7 @@ $ rm -rf .git
$ git init
$ git remote add origin `您的git仓库地址`
$ npm install
$ npm run dev #可执行npm start跳过ESlint检查。
$ npm run dev # 可执行npm start跳过ESlint检查。
```
## 调试说明
@ -79,14 +79,10 @@ http://pm2.keymetrics.io/docs/usage/docker-pm2-nodejs/
### Docker部署说明
```
$ docker pull node
$ docker run -itd --name RESTfulAPI -v "$PWD":/usr/src/app -w /usr/src/app node node ./dist/app.js
$ docker run -itd --name RESTfulAPI -v '$PWD':/usr/src/app -w /usr/src/app node node ./dist/app.js
```
通过
```
$ docker ps
```
查看是否运行成功及运行状态
通过'docker ps'查看是否运行成功及运行状态
### Linux/Mac 直接后台运行生产环境代码
有时候为了简单,我们也这样做:
@ -94,7 +90,7 @@ $ docker ps
$ nohup node ./dist/app.js > logs/out.log &
```
查看运行状态(如果有"node app.js"出现则说明正在后台运行):
查看运行状态(如果有'node app.js'出现则说明正在后台运行):
```
$ ps aux|grep app.js
```
@ -110,7 +106,7 @@ $ tail -f logs/out.log
```
### 配合Vue-cli部署说明
Vue-cliVue2运行"npm run build"后会在"dist"目录中生成所有静态资源文件。推荐使用Nginx处理静态资源以达最佳利用效果然后通过上述任意一种方法部署RESTful API服务器。前后端是完全分离的请注意Koa2 RESTful API Server项目中config/main.json里面的跨域配置。
Vue-cliVue2运行'npm run build'后会在'dist'目录中生成所有静态资源文件。推荐使用Nginx处理静态资源以达最佳利用效果然后通过上述任意一种方法部署RESTful API服务器。前后端是完全分离的请注意Koa2 RESTful API Server项目中config/main.json里面的跨域配置。
推荐的Nginx配置文件
```
@ -150,23 +146,37 @@ $ docker run -itd -p 80:80 -p 443:443 -v `pwd`/nginx_config:/etc/nginx/conf.d ng
> 引入插件的版本将会持续更新
引入的插件:
`` koa nodemon babel-cli babel-register babel-plugin-transform-runtime babel-preset-es2015 babel-preset-stage-2 gulp gulp-eslint eslint eslint-config-standard eslint-friendly-formatter eslint-plugin-html eslint-plugin-promise ``
`` koa@2 koa-bodyparser@next koa-router@next koa-session2 koa-static2 koa-compose require-directory babel-cli babel-register babel-plugin-transform-runtime babel-preset-es2015 babel-preset-stage-2 gulp gulp-eslint eslint eslint-config-standard eslint-friendly-formatter eslint-plugin-html eslint-plugin-promise nodemailer promise-mysql ``
"koa-bodyparser": "^3.2.0",
"koa-compose": "^3.1.0",
"koa-convert": "^1.2.0",
"koa-favicon": "^2.0.0",
"koa-json": "^1.1.3",
"koa-logger": "^1.3.0",
"koa-multer": "^1.0.0",
"koa-router": "^7.0.1",
"koa-session": "^3.3.1",
"koa-static2": "^0.1.8",
"koa-onerror": "^3.0.1",
koa-multer': '^1.0.0',
**koa**: HTTP框架
**koa2**: HTTP框架
 Synopsis: HTTP framework.
 From: https://github.com/koajs/koa
 From: https://github.com/koajs/koa v2
**koa-bodyparser**: body解析器
 Synopsis: A body parser for koa, base on co-body. support json, form and text type body.
 From: https://github.com/koajs/logger
**koa-router**: Koa路由
 Synopsis: Router middleware for koa.
 From: https://github.com/alexmingoia/koa-router/tree/master/
**koa-session2**: Session中间件
 Synopsis: Middleware for Koa2 to get/set session.
 From: https://github.com/Secbone/koa-session2
**koa-static2**: 静态资源中间件
 Synopsis: Middleware for Koa2 to serve a folder under a name declared by user.
 From: https://github.com/Secbone/koa-static2
**koa-compose**: 多个中间件组合成一个
 Synopsis: Compose several middleware into one.
 From: https://github.com/koajs/compose
**require-directory**: 递归遍历指定目录
 Synopsis: Recursively iterates over specified directory.
 From: https://github.com/troygoode/node-require-directory
**babel-cli**: Babel编译ES6代码为ES5代码
 Synopsis: Babel is a JavaScript compiler, ES6 to ES5.
@ -201,7 +211,7 @@ $ docker run -itd -p 80:80 -p 443:443 -v `pwd`/nginx_config:/etc/nginx/conf.d ng
 From: https://github.com/feross/eslint-config-standard
**eslint-friendly-formatter**: 使得ESlint提示在Sublime Text或iterm2中更友好Atom也有对应的ESlint插件。
 Synopsis: A simple formatter/reporter for ESLint that's friendly with Sublime Text and iterm2 "click to open file" functionality
 Synopsis: A simple formatter/reporter for ESLint that's friendly with Sublime Text and iterm2 'click to open file' functionality
 From: https://github.com/royriojas/eslint-friendly-formatter
**eslint-plugin-html**: 检查HTML文件中的JS代码规范
@ -214,13 +224,36 @@ $ docker run -itd -p 80:80 -p 443:443 -v `pwd`/nginx_config:/etc/nginx/conf.d ng
**eslint-plugin-promise**: ESlint依赖项
 Synopsis: ESlint Rules for the Standard Linter.
 From: https://github.com/xjamundx/eslint-plugin-standard
 From: https://github.com/xjamundx/eslint-plugin-standard
**nodemailer**: 发送邮件
 Synopsis: Send e-mails with Node.JS.
 From: https://github.com/nodemailer/nodemailer
**promise-mysql**: 操作MySQL数据库依赖
 Synopsis: Promise Mysql.
 From: https://github.com/lukeb-uk/node-promise-mysql
支持Koa2的中间件列表https://github.com/koajs/koa/wiki
其它经常配合Koa2的插件
**其它经常配合Koa2的插件**
**koa-nunjucks-2**:
一个好用的模版引擎可用于前后端nunjuckshttps://github.com/mozilla/nunjucks
一个好用的模版引擎可用于前后端nunjuckshttps://github.com/mozilla/nunjucks
**koa-favicon**:
Koa的favicon中间件https://github.com/koajs/favicon
**koa-server-push**:
HTTP2推送中间件https://github.com/silenceisgolden/koa-server-push
**koa-convert**: 转换旧的中间件支持Koa2
 Synopsis: Convert koa generator-based middleware to promise-based middleware.
 From: https://github.com/koajs/convert
**koa-logger**: 请求日志输出,需要配合上面的插件使用
 Synopsis: Development style logger middleware for Koa.
 From: https://github.com/koajs/logger
## 目录结构说明
@ -240,13 +273,18 @@ $ docker run -itd -p 80:80 -p 443:443 -v `pwd`/nginx_config:/etc/nginx/conf.d ng
├── config
│   └── main.js # 主配置文件(*谨防泄密!)
├── src # 源代码目录,编译后目标源代码位于 dist 目录
│   ├── app.js # koa 配置
│   ├── config # 配置目录
│   ├── app.js # 入口文件
│   ├── plugin # 插件目录
│   └── smtp_sendemail # 示例插件 - 发邮件
│   ├── tool # 工具目录
│   ├── PluginLoader.js # 插件引入工具
│   └── Common.js # 示例插件 - 发邮件
│   ├── lib # 库目录
│   ├── controllers # 控制器
│   ├── index.js # 入口文件
│   ├── models # 模型
│   ├── routes # 路由
│   └── services # service
│   └── services # 服务
├── assets # 静态资源目录
└── logs # 日志目录
```
@ -255,15 +293,15 @@ $ docker run -itd -p 80:80 -p 443:443 -v `pwd`/nginx_config:/etc/nginx/conf.d ng
### AngularJS (Ionic同)
```
$http({
method: "post",
url: "http://localhost:3000/xxx",
data: {para1:"para1",para2:"para2"},
method: 'post',
url: 'http://localhost:3000/xxx',
data: {para1:'para1',para2:'para2'},
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}).success(function (data) {
}).error(function (data) {
});
})
```
### jQuery
@ -271,27 +309,27 @@ $http({
$.ajax({
cache: false,
type: 'POST',
url: "http://localhost:3000/xxx",
url: 'http://localhost:3000/xxx',
data: {
para1: para1
},
async: false,
dataType: "json",
dataType: 'json',
success: function (result) {
},
error: function (err) {
console.log(err);
console.log(err)
}
});
})
// 上传文件
//创建FormData对象
var data = new FormData();
var data = new FormData()
//为FormData对象添加数据
//
$.each($('#inputfile')[0].files, function (i, file) {
data.append('upload_file', file);
});
data.append('upload_file', file)
})
$.ajax({
url: 'http://127.0.0.1:3000/api/upload_oss_img_demo',
type: 'POST',
@ -300,38 +338,38 @@ $.ajax({
contentType: false, //不可缺
processData: false, //不可缺
success: function (data) {
console.log(data);
if (data.result == "ok") {
$("#zzzz").attr("src", data.img_url);
console.log(data)
if (data.result == 'ok') {
$('#zzzz').attr('src', data.img_url)
}
}
});
})
```
### MUI
```
mui.ajax({ url: "http://localhost:3000/xxx", dataType: "json",
mui.ajax({ url: 'http://localhost:3000/xxx', dataType: 'json',
success: function(data){
},
error: function(data){
console.log("error!");
console.log('error!')
}
});
})
```
### JavaScript
```
var xhr = new XMLHttpRequest()
xhr.open("POST", "http://localhost:3000/xxx", true) //POST或GETtrue异步或 false同步
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded")
xhr.open('POST', 'http://localhost:3000/xxx', true) //POST或GETtrue异步或 false同步
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded')
xhr.withCredentials = true
xhr.onreadystatechange = function () {
if (obj.readyState == 4 && obj.status == 200 || obj.status == 304) {
var gotServices = JSON.parse(xhr.responseText)
}else{
console.log("ajax失败了")
console.log('ajax失败了')
}
}
xhr.send({para1: para1})
@ -342,8 +380,8 @@ https://github.com/pagekit/vue-resource
```
// global Vue object
Vue.http.post('/someUrl', [body], {
headers: {"Content-type", "application/x-www-form-urlencoded"}
}).then(successCallback, errorCallback);
headers: {'Content-type', 'application/x-www-form-urlencoded'}
}).then(successCallback, errorCallback)
```
### fetch
@ -365,7 +403,7 @@ fetch('/users', {
})
// 文件上传
var input = document.querySelector('input[type="file"]')
var input = document.querySelector('input[type='file']')
var data = new FormData()
data.append('file', input.files[0])
@ -382,7 +420,7 @@ https://github.com/visionmedia/superagent
```
request.post('/user')
.set('Content-Type', 'application/json')
.send('{"name":"tj","pet":"tobi"}')
.send('{'name':'tj','pet':'tobi'}')
.end(callback)
```
@ -391,6 +429,6 @@ request.post('/user')
## 彻底移除ESlint方法
删除package.json的devDependencies中所有eslint开头的插件根目录下的“.eslintignore、.eslintrc.js”文件并且修改package.json的dev为
```
"dev": "gulp start"
'dev': 'gulp start'
```
删除gulpfile.js中的lint、eslint_start两个任务并且把default改为“gulp.task('default', ['start']”。

1
assets/update/.gitkeep Normal file
View File

@ -0,0 +1 @@
1

View File

@ -1,2 +1,2 @@
require("babel-register")
require("../src/app")
require('babel-register')
require('../src/app')

View File

@ -0,0 +1,15 @@
import path from 'path'
export let SystemConfig = {
HTTP_server_type: 'http://', // HTTP服务器地址,包含"http://"或"https://"
HTTP_server_host: 'localhost',// HTTP服务器地址,请勿添加"http://"
HTTP_server_port: '3000',// HTTP服务器端口号
System_country: 'zh-cn', // 所在国家的国家代码
System_plugin_path: path.join(__dirname, "plugins/"), // 插件路径
mysql_host: 'localhost', // MySQL服务器地址
mysql_user: 'root', // 数据库用户名
mysql_password: 'root', // 数据库密码
mysql_database: 'test', // 数据库名称
mysql_port: 3306, // 数据库端口号
mysql_prefix: 'api_' // 默认"api_"
}

View File

@ -10,7 +10,14 @@
"production": "node dist/app.js"
},
"dependencies": {
"koa": "^1.2.4"
"koa": "^2.0.0",
"koa-bodyparser": "^3.2.0",
"koa-compose": "^2.5.1",
"koa-router": "^7.0.1",
"koa-session2": "^1.0.8",
"nodemailer": "^2.7.2",
"promise-mysql": "^3.0.0",
"require-directory": "^2.1.1"
},
"devDependencies": {
"babel-cli": "^6.22.2",
@ -26,7 +33,8 @@
"eslint-plugin-standard": "^2.0.1",
"gulp": "^3.9.1",
"gulp-eslint": "^3.0.1",
"gulp-nodemon": "^2.2.1"
"gulp-nodemon": "^2.2.1",
"koa-logger": "^1.3.1"
},
"engines": {
"node": ">= 6.9.4",

View File

@ -1,3 +1,57 @@
import Koa2 from 'koa'
import KoaBodyParser from 'koa-bodyparser'
import KoaSession from 'koa-session2'
import KoaStatic from 'koa-static2'
import { SystemConfig } from '../config/main.js'
import path from 'path'
console.log('ok')
console.log('aaa')
import MainRoutes from './routes/main-routes'
import ErrorRoutes from './routes/error-routes'
import PluginLoader from './tool/PluginLoader'
const app = new Koa2()
const BodyParser = new KoaBodyParser()
const env = process.env.NODE_ENV || 'development' // Current mode
app.use(BodyParser({
detectJSON: function (ctx) {
return /\.json$/i.test(ctx.path)
},
extendTypes: {
json: ['application/x-javascript'] // will parse application/x-javascript type body as a JSON string
},
onerror: function (err, ctx) {
ctx.throw('body parse error:' + err, 422)
}
})) // Processing request
.use(KoaStatic('assets', path.resolve(__dirname, '../assets'))) // Static resource
.use(KoaSession({key: 'RESTfulAPI'})) // Set Session 生产环境务必随机设置一个值
.use(PluginLoader(SystemConfig.System_plugin_path))
.use((ctx, next) => {
if (ctx.request.header.host.split(':')[0] === 'api.XXX.com' || ctx.request.header.host.split(':')[0] === '127.0.0.1') {
ctx.set('Access-Control-Allow-Origin', '*')
ctx.set('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept')
ctx.set('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS')
ctx.set('Access-Control-Allow-Credentials', true) // 允许带上 cookie
}
return next()
})
.use(MainRoutes.routes())
.use(MainRoutes.allowedMethods())
.use(ErrorRoutes())
if (env === 'development') { // logger
app.use((ctx, next) => {
const start = new Date()
return next().then(() => {
const ms = new Date() - start
console.log(`${ctx.method} ${ctx.url} - ${ms}ms`)
})
})
}
app.listen(SystemConfig.HTTP_server_port)
console.log('Now start HTTP server on port ' + SystemConfig.HTTP_server_port + '...')
export default app

0
src/controllers/api.js Normal file
View File

2
src/controllers/index.js Normal file
View File

@ -0,0 +1,2 @@
let requireDirectory = require('require-directory')
module.exports = requireDirectory(module)

52
src/lib/mysql.js Normal file
View File

@ -0,0 +1,52 @@
import mysql from 'promise-mysql'
import { SystemConfig } from '../config.js'
import { SqlFormat } from '../tool/common_tool.js'
let pool = mysql.createPool({
// connectionLimit: 4, // 连接池最多可以创建的连接数
host: SystemConfig.mysql_host,
user: SystemConfig.mysql_user,
password: SystemConfig.mysql_password,
database: SystemConfig.mysql_database,
port: SystemConfig.mysql_port,
insecureAuth: true
})
// 执行一行SQL语句并返回结果
export let query = (sql) => {
return pool.query(SqlFormat(sql))
}
// 执行多行SQL语句并返回结果
export let querys = (sqls) => {
let keys = Object.keys(sqls)
let list = Object.values(sqls)
let promises = list.map(function (sql) {
return query(sql)
})
return Promise.all(promises).then(data => {
let result = {}
for (let index in data) {
result[keys[index]] = data[index]
}
return result
})
}
// 返回连接
export let getSqlConnection = () => {
return pool.getConnection().disposer(function (connection) {
pool.releaseConnection(connection)
})
}
// 连接使用方法
// var Promise = require("bluebird")
// Promise.using(getSqlConnection(), function(connection) {
// return connection.query('select `name` from hobbits').then(function(row) {
// return process(rows)
// }).catch(function(error) {
// console.log(error)
// })
// })

2
src/models/index.js Normal file
View File

@ -0,0 +1,2 @@
let requireDirectory = require('require-directory')
module.exports = requireDirectory(module)

View File

@ -0,0 +1,38 @@
import nodemailer from 'nodemailer'
// 发送Email目前使用的是阿里云SMTP发送邮件
// receivers 目标邮箱,可以用英文逗号分隔多个。(我没试过)
// subject 邮件标题
// text 文本版本的邮件内容
// html HTML版本的邮件内容
// 返回
// result 200是成功500是失败
// info 是返回的消息,可能是结果的文本,也可能是对象。(这个错误不要暴露给用户)
export let sendemail = (receivers, subject, text, html) => {
return new Promise(function (resolve) {
let transporter = nodemailer.createTransport('smtp://postmaster%40abcd.com:password@smtp.abcd.com')
// setup e-mail data with unicode symbols
let mailOptions = {
from: '"XX平台 👥" <postmaster@abcd.com>', // sender address
to: receivers,
subject: subject,
text: text || 'Hello world 🐴', // plaintext body
html: html || '<b>Hello world 🐴</b>' // html body
}
transporter.sendMail(mailOptions, function (error, info) {
if (error) {
resolve({
result: 500,
info: error
})
} else {
resolve({
result: 200,
info: info.response
})
}
})
})
}

View File

@ -0,0 +1,10 @@
module.exports = function () {
return function (ctx, next) {
switch (ctx.status) {
case 404:
ctx.body = '没有找到内容 - 404'
break
}
return next()
}
}

15
src/routes/main-routes.js Normal file
View File

@ -0,0 +1,15 @@
import KoaRouter from 'koa-router'
// import controllers from '../controllers/index.js'
const router = new KoaRouter()
router
.get('/', function (ctx, next) {
ctx.body = '禁止访问!'
}) // HOME 路由
// .get('/api/:api_type/:name', controllers.api.api_get)
// .put('/api/:api_type/:name', controllers.api_put.api_put
// .post('/api/:api_type/:name', controllers.api.default)
// .delect('/api/:api_type/:name', controllers.api.default)
module.exports = router

1
src/services/.gitkeep Normal file
View File

@ -0,0 +1 @@
1

63
src/tool/Common.js Normal file
View File

@ -0,0 +1,63 @@
import {
SystemConfig
} from '../config/main.js'
// 截取字符串,多余的部分用...代替
export let setString = (str, len) => {
let StrLen = 0
let s = ''
for (let i = 0; i < str.length; i++) {
if (str.charCodeAt(i) > 128) {
StrLen += 2
} else {
StrLen++
}
s += str.charAt(i)
if (StrLen >= len) {
return s + '...'
}
}
return s
}
// 格式化设置
export let OptionFormat = (GetOptions) => {
let options = '{'
for (let n = 0; n < GetOptions.length; n++) {
options = options + '\'' + GetOptions[n].option_name + '\':\'' + GetOptions[n].option_value + '\''
if (n < GetOptions.length - 1) {
options = options + ','
}
}
return JSON.parse(options + '}')
}
// 替换SQL字符串中的前缀
export let SqlFormat = (str) => {
if (SystemConfig.mysql_prefix !== 'bm_') {
str = str.replace(/bm_/g, SystemConfig.mysql_prefix)
}
return str
}
// 数组去重
export let HovercUnique = (arr) => {
let n = {}
let r = []
for (var i = 0; i < arr.length; i++) {
if (!n[arr[i]]) {
n[arr[i]] = true
r.push(arr[i])
}
}
return r
}
// 获取json长度
export let getJsonLength = (jsonData) => {
var arr = []
for (var item in jsonData) {
arr.push(jsonData[item])
}
return arr.length
}

34
src/tool/PluginLoader.js Normal file
View File

@ -0,0 +1,34 @@
import fs from 'fs'
import path from 'path'
import compose from 'koa-compose'
function getDirs (srcpath) {
return fs.readdirSync(srcpath).filter(file => {
return fs.statSync(path.join(srcpath, file)).isDirectory()
})
}
module.exports = (srcpath, filename = 'index.js') => {
let plugins = {}
let dirs = getDirs(srcpath)
let list = []
for (let name of dirs) {
let fn = require(path.join(srcpath, name, filename))
if (typeof fn !== 'function' && typeof fn.default === 'function') {
fn = fn.default
} else {
throw (new Error('plugin must be a function!'))
}
plugins[name] = fn
list.push(function (ctx, next) {
return fn(ctx, next) || next()
})
}
return compose(list)
}