基于 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 // 模版
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 用于数据接口调用,规定数据格式和调用方式
在 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 用于构建数据对象
在 models 目录中添加配置文件
可以通过 bucky model MyModel
创建
model 的命名必须首字母大写(通过 bucky-cli 会自动给你转化首字母),必须是个类
export default class MyModel {
// 实例初始化方法
constructor () {}
// 静态方法
static staticFunction () { }
// 静态同步方法
static async staticAsyncFunction () { }
// 实例方法
staticFunction () { }
// 实例同步方法
static async asyncFunction () { }
}
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 表单
// 使用 ctx.user 获取当前用户信息 (登陆的情况下)
// 使用 ctx.isAppWebView() 可以获取到是否在 link 或者 掌链 中
// 使用 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 是对数据返回的封装
默认封装为
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 是模版渲染的模版的存放位置
可以在 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 配置
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 配置
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注入
书写自定义 koa 插件,也可以改造现有 koa 插件进来
当在 configs/app.js 中设置 _: true
, 即 全局中 _
即位 lodash 句柄,哪都可以用
通过 ctx.appWebView
或者 ctx.request.appWebView
方法获取
ctx.appWebView()
判断目前是在 link 或者 链家app 中ctx.appWebView('link')
判断目前是在 link 中ctx.appWebView('link')
判断目前是在 链家app 中
-
如果 接口通过 '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
方式获取(该方式不可能是文件)
设置 configs/static.js
export default {
// 静态路径
staticPath: '/public',
// 静态文件夹路径
staticDir: path.resolve(__dirname, '../statics'),
// 文件是否返回 etag 头
etag: true,
// 文件是否返回 Last-Modified 头
lastModified: true,
// 用户客户端缓存时间
maxAge: 60
}
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。一般这个文件不需要做修改。已经统一配置了。
npm install moment
A lightweight JavaScript date library for parsing, validating, manipulating, and formatting dates.
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.
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.