@lianjia-fe/bucky-core
v0.1.2
Published
a node web server's core
Downloads
1
Readme
Bucky
基于 koa 开发的 node 框架 with ♥︎ by lianjia-fe
__ __
| |--.--.--.----.| |--.--.--.
| _ | | | __|| <| | |
|_____|_____|____||__|__|___ |
|_____|
with ♥︎ by lianjia-fe
使用方式
通过 bucky-cli 生成你的项目 参见 https://github.com/LianjiaTech/bucky-cli
之后通过下面命令启动
$ npm install
$ npm run dev
项目结构
your-project
├── bin // 上线 op 启动脚本
└── src // 项目目录
├── actions // 动作
├── apis // 接口
├── configs // 配置
├── models // 数据对象
├── responses // 返回封装
├── services // 插件
└── views // 模版
CONFIG
bucky 很多工作都可以使用配置完成,配置的设置会分布在各个地方 需要了解 配置中环境的覆盖特征
通过 configs/nev 中可以设置当前的环境,默认使用 process.env.NODE_ENV 来外部注入
下面是 configs/app 的例子
import prodConfig from './_prod'
export default {
name: PKG.name,
// 项目监听的端口
port: prodConfig.base.PORT,
// 是否全局绑定 lodash
_: true,
// 是否前端有代理
// 如果你的server 前面还有 nginx 等的代理则选择 true
proxy: true
}
// 下面的特定环境可以深度合并到上面的默认环境
// 线上环境是上面的默认环境,不要乱改哦
// 开发环境配置
export const development = {
port: 3000,
proxy: false
}
// 测试环境配置
export const testing = {
port: 3000,
proxy: false
}
在 default, development, testing 中可以写不同的配置。
- 在 production 环境中 配置为 default
- 在 development 环境中 配置为 default + development
- 在 testing 环境中 配置为 default + testing
数据是 两个数据的 深度 merge 得到的。
API
api 用于数据接口调用,规定数据格式和调用方式
在 apis 目录中添加配置文件,会自动生成api
可以通过 bucky api myAPI
创建
export default api => {
api.config = {
// 接口超时时间
timeout: 10000,
// 默认的返回值简单约定, {code: , data:, msg:} 的形式返回
// 如果是这种返回形式的话,那么通过下面参数,做接口检查
codeKey: 'code',
dataKey: 'data',
messageKey: 'msg',
// 可以使用以下方法 处理 api 请求前的数据
// requestHandler () {}
// 可以使用以下方法 处理 api 返回的数据
// responseHandler () {}
// 当 [codeKey] === [successCode] 是认为请求成功
successCode: 100000,
// 接口的协议,域名,端口
base: 'http://[domain]:[port]',
// 上行格式 可以是:
// application/x-www-form-urlencoded
// multipart/form-data
// application/json
// 等等
// contentType: 'application/x-www-form-urlencoded'
}
// 定义接口 这边的设置是在 config 基础上的
// 内部的没有点的值复用 config 中的
api('__api_name__', {
// 接口 path, 可以使用 {xxx} 的方法动态修改 uri
// 在请求的 uriReplacer 参数设置
uri: 'i/{am}/a/path',
// 接口访问方式 get | post | delete | put ...
method: 'get',
// 接口参数检查
parameters: {
// string: api.type.string.required,
// number: api.type.number.required
},
// 是否使用缓存
// 简单的缓存设置, 写使用的 redis 名即可
// cache: 'cache',
// 如果想自定义,可以使用下面的
// cache: {
// redis: 'cache',
// key: req => req.method === 'GET' ? req.uri : null,
// fromCache: cache => JSON.parse(cache),
// toCache: data => JSON.stringify(data)
// }
})
// api(....) 继续定义下一个接口
}
生成完之后,你可以用以下的方式调用
await API.[文件名].[定义接口名](data, option)
参数定义:
data
{Object}: 接口请求参数,通过不同的 method,作为 search 或者 bodyoption
{Object}: 可选 配置query
{Object}: 可选 post或着其他将data转化为body的时候动态加一些 queryuriReplacer
{Object}: 可选 可以替换uri上 {xxxx} 中的值,动态修改uriqueryEncode
{Boolean}: 可选 默认true 是否将 query 中的 value 做 encodeURIComponent
Model
Model 用于构建数据对象
在 models 目录中添加配置文件
可以通过 bucky model MyModel
创建
model 的命名必须首字母大写(通过 bucky-cli 会自动给你转化首字母),必须是个类
export default class MyModel {
// 实例初始化方法
constructor () {}
// 静态方法
static staticFunction () { }
// 静态同步方法
static async staticAsyncFunction () { }
// 实例方法
staticFunction () { }
// 实例同步方法
static async asyncFunction () { }
}
Action
action 是最终业务实现。一个业务对应一个action
在 actions 目录中添加配置文件
可以通过 bucky action /my/path/here
创建
在没有其他干扰的情况下, action 文件的目录路径即为访问路径
action 在 configs/action 有一些配置
defaultAction
: action 中的默认值
一个action的配置
export default {
// csrf 作用是判断来访者身份,如果是 false 不会判断
// 默认操作是,如果是 post, put, delete 操作,并且 来访者 域名和当前域名不匹配则会拒绝访问
// 如果 csrf 为 字符串 或者 字符串 数组(可以是minimatch),则相当于设置白名单,白名单中的会被认可
// csrf: false,
// csrf: [
// '*.lianjia.com', // minimatch
// 'aaa.lianjia.com', // 域名
// '*', // 匹配所有
// '' // 允许 空 referer
// ],
// cors 是否允许 异步跨域访问
// cors: '*',
// cors: 'http://lianjia.com',
// cors: {
// origin: 'http://lianjia.com',
// methods: 'GET, POST',
// Headers: 'X-Requested-With'
// },
// 如果路由命中则会走这个方法
// 可以在 rewrite redirect 修改路由指向
async handler (ctx) {
// ctx 为 koa 的 context 上下文
// 详见: http://koajs.com/#context
// ctx.request 为 koa request 对象
// 详见: http://koajs.com/#request
// ctx.response 为 koa response 对象
// 详见: http://koajs.com/#response
// 使用 ctx.request.query 获取 url query 参数
// 使用 ctx.request.body 获取通过 x-www-form-urlencoded 方式提交的 body
// 使用 ctx.request.form 获取 formData 方式提交的 form 表单
// 使用 API.xxx, Model.xxx 调用 api model 中声明的 接口 和 类
// 使用 ctx.render 渲染模版
// 默认使用 ejs, 可以在 config/view 中设置
// ctx.render('index', { title: 'bucky' })
// 页面未找到
// ctx.notFound()
// 页面无权限
// ctx.forbidden()
// 页面跳转
// ctx.redirect('http://lianjia.com')
// 页面跳转(使用js方式)
// ctx.redirect('http://lianjia.com', {viaJavascript: true})
// 系统错误,并且报错
// ctx.serverError(new Error('server error'))
// ajax 返回
// ctx.ajax({greeting: 'hello world'})
// ajax 返回异常
// ctx.ajax(null, {error: true, message: '加载失败'})
},
// 如果 handler 代码出错,可以在 catchError 中捕获处理错误
// 如果没有 catchError, 则会自动抛 500 错误,并返回错误内容
// async catchError (ctx, error) { },
}
Response
response 是对数据返回的封装
默认封装为
ctx.notFound() 返回页面不存在 使用 404.ejs 模版渲染空页面
ctx.forbidden() 返回页面没权限 使用 403.ejs 模版渲染空页面
ctx.serverError(error) 返回页面服务错误 使用 500.ejs 模版渲染空页面
error
{Error} 错误对象
ctx.redirect(url, {viaJavascript}) 返回页面需要跳转 使用 302.ejs 模版渲染空页面
url
{String} 跳转的页面地址viaJavascript
{Boolean} 是否使用 javascript 的方式跳转
ctx.ajax(data, {error, message}) 渲染 ajax 数据返回(json)
data
{Any} 可选 返回数据对象error
{Boolean|Any} 可选 是否错误(默认错误类型), 制定错误类型 (错误类型在 configs/response 中规定)message
{Any} 可选 错误的描述
ctx.jsonp(data, {error, message, handlerKey}) 渲染 jsonp 数据返回(json)
data
{Any} 可选 返回数据对象error
{Boolean|Any} 可选 是否错误(默认错误类型), 制定错误类型 (错误类型在 configs/response 中规定)message
{Any} 可选 错误的描述handlerKey
{String} 可选 jsonp 钩子的 query 的 key
ctx.render(templatePath, data, {layout})
templatePath
{String} 对应的 views 目录下的文件的路径,从 views 算 relative 路径data
{Object} 渲染模版的数据layout
{False|String} 渲染模版外层layout模版(false为没有)
View
view 是模版渲染的模版的存放位置
可以在 configs/view 中设置模版引擎配置
export default {
// 你想使用的模版类型,默认是 ejs
template: 'ejs',
// 项目 外层框架的模版 位置 (相对于 views 文件夹)
layout: 'layout',
// 拼装模版数据,在这边写,每次都会带上
// 可以在这边定义 静态文件的版本号 之类的东西
data: {
title: 'bucky'
},
// ejs 的配置
ejs: {
compileDebug: false,
delimiter: '%',
ext: '.ejs'
}
}
跳转
跳转规则设置
可以修改 configs/redirect 和 configs/rewrite 来修改跳转,
区别是 redirect 是通过 302 来实现的, rewrite 是通过内部修改 url 的情况实现的。
在 rewrite 中可以通过 ctx.originalUrl
为转化之前的 url
export default [
// rewrite 规则
// 动态改变 url 让其走到对应的路由
// 是 app 内部逻辑
// 规则从上到下,如果匹配则不会再往下走了
// from 可以是 正则,方法,字符串
// 当url 被 正则匹配 或 方法返回非空 或 字符串匹配 minimatch
// 则会走到对应的 to
// to 可以是字符串,也可以是方法
// 如果是方法 参数接收 正则匹配的match值, 方法返回值, 字符串是否匹配的布尔值
// 返回为 字符串
// 这个例子是改写 页面图标 的路由
{ from: '/favicon.ico', to: '/public/favicon.ico' }
]
Redis
设置 redis 配置
export default {
// 配置一个名为 "cache" 的 redis
// 参见 http://redis.js.org/#api-rediscreateclient
cache: {
host: '127.0.0.1',
port: '6379',
}
}
使用
可以通过对应配置中 redis 名称,在全局 Redis.[redis 名称] 来获取对应的 redis 使用池 在会掉闭包中执行redis命令, 不需要释放 redis 句柄
记得对应的 async await 的使用 具体 redis 命令 查看 http://devdocs.io/redis/
const result = await Redis.cache(async redis => {
await redis.hset(cacheKey, name, value)
return await redis.hget(cacheKey, name)
})
hashCache
因为常见的 redis 使用是缓存,所以可以使用封装的 hashCache 方法,更加简单调用
await Redis.cache.hashCache.get('some_name', 'some_value')
await Redis.cache.hashCache.set('some_name')
通过 hashCache 使用默认的 redis key 存储对象 你可以通过 name_space 来区分存储 hash 池
await Redis.cache.hashCache('name_space').get('some_name', 'some_value')
await Redis.cache.hashCache('name_space').set('some_name')
MySQL
设置 MySQL 配置
export default {
// 配置一个名为 "store" 的 mysql
// 参见 https://www.npmjs.com/package/mysql#pooling-connections
store: {
host: '127.0.0.1',
port: '3306',
user: 'root',
password: '123456',
database: 'test'
}
}
使用
可以通过对应配置中 mysql 名称,在全局 MySQL.[mysql 名称] 来获取对应的 mysql 使用池 在会掉闭包中执行mysql命令, 不需要释放 mysql 句柄
记得对应的 async await 的使用
const items = await MySQL.store(async store => {
return await store.query(`select * from pet where name='${name}'`)
})
await MySQL.store(async store => {
await store.query(`insert into pet(type, name) values('${type}','${name}')`)
})
可以使用 https://www.npmjs.com/package/sql 来声称 sql语句。 更加方便,并且避免sql注入
插件 service
书写自定义 koa 插件,也可以改造现有 koa 插件进来
Lodash
当在 configs/app.js 中设置 _: true
, 即 全局中 _
即位 lodash 句柄,哪都可以用
已经封装的功能
Form Parse / Body Parse
如果 接口通过 'multipart/form-data' 或者 'application/octet-stream' 等方式发送的 formData 请求,请使用
ctx.form
或者ctx.request.form
方式获取,如果是文件类型,返回是stream如果 接口通过 'application/x-www-form-urlencoded' 或者 'application/json' 等方式请求的,请使用
ctx.body
或者ctx.request.body
方式获取(该方式不可能是文件)
Static Server (静态资源)
设置 configs/static.js
export default {
// 静态路径
staticPath: '/public',
// 静态文件夹路径
staticDir: path.resolve(__dirname, '../statics'),
// 文件是否返回 etag 头
etag: true,
// 文件是否返回 Last-Modified 头
lastModified: true,
// 用户客户端缓存时间
maxAge: 60
}
默认 Models
Utils
Model.Utils 提供一些可使用的方法集合
- appendSearch 往 uri 上追加 search
- @param {Object} object
- @param {Boolean} encode
- @return {String}
- assert 错误检查,如果命中抛异常
- @param {Boolean} 错误检查
- @param {String} 输出信息模版
- @param {...any} 模版的参数
request requset 库
requestAsync 异步 request 封装,同 requset 库
escapeHTML 转移 HTML 特殊字符
- @param {String}
- @param {String}
- deepMerge 深拷贝
- @param {Object}
- @param {Object}
- @param {Object}
- formatPath 格式化 url 路径
- @param {String}
- @return {String}
- /fsdfsd/fdsfdsf//fds///dfsds/ => /fsdfsd/fdsfdsf/fds/dfsds
- getPkg 获取 package.json
- @return {Object}
- getType getType 获取对象类型
- @param {any} object
- @return {string} boolean|number|string|function|array|date|regexp|object|error
- isIP 是否是 ip 格式
- @param {String}
- @param {Boolean}
- match 匹配字符串
- @param {String} string
- @param {Regexp|Function|String} rule
- @param {Any} 给 Function 类型的 rule 添加参数
- @return {any}
- md5 返回md5值
- @param {String} string
- @return {String}
noop 空方法
object2Search 将 object 转化成 query
- @param {Object} object
- @param {Boolean} encode
- @return {String}
- objectForEach 循环对象元素
- @param {Object} object Object, 最好是一个纯对象
- @param {Function} iteratee 循环函数
- @return {Object} 类似Array.map 将 iteratee 返回的数据组成新对象
- promisify 将 node callback 方式转化成 promise 的方式
- @param {Function} originalFunction
- @param {any} context
- @param {Boolean} returnMulitArgs
- @return {Promise}
- string2Array 转换字符串到数组
- @param {String} string
- @param {String} spliter
- @return {Array}
- uuid 获取随机 id
- @return {String}
测试部署
确保测试的 node 环境 >= 6.4.4
推荐测试安装 pm2 进行管理 node 服务,在 测试机 使用 npm install -g pm2
的方式安装。
测试可以通过 pm2 list
, pm2 start
, pm2 delete
等命令管理服务,简单明了。
安装完 pm2 后,通过如下代码启动 node 服务(测试环境)
pm2 delete <项目名,测试自己定,不要和其他项目重复就可以>
cd <项目源码目录>
pm2 start npm --name "<项目名,测试自己定,不要和其他项目重复就可以>" -- run test
如果 pm2 命令找不到,请自行做下映射关系 /usr/local/node/bin/pm2 -> pm2
上线部署
需要注意,在
configs/_prod.js
文件,这个文件会读取 op 服务的配置。需要修改hostName
变量为你的上线文件夹名(基本是你上线域名),其他代码一般不需要修改。线上是通过
bin/run.js
启动服务的,而不是 npm script。一般这个文件不需要做修改。已经统一配置了。
推荐第三方 node modules
moment
npm install moment
A lightweight JavaScript date library for parsing, validating, manipulating, and formatting dates.
mathjs
npm install mathjs
Math.js is an extensive math library for JavaScript and Node.js. It features a flexible expression parser with support for symbolic computation, comes with a large set of built-in functions and constants, and offers an integrated solution to work with different data types like numbers, big numbers, complex numbers, fractions, units, and matrices. Powerful and easy to use.
accounting-js
npm install accounting-js
accounting-js is a tiny JavaScript library for number, money and currency parsing/formatting. It's lightweight, fully localisable, has no dependencies, and works great client-side or server-side. Use standalone or as a nodeJS/npm and AMD/requireJS module.