tofu(豆腐) 应用 framework, 开箱即用,使得基于openresty开发应用时更加丝般顺滑
[TOC]
alpha 阶段, 目前仅支持 linux/unix 环境
- 基于openresty
- middleware方式流程处理
- 所有功能使用extend方式
- 内置常用工具包脚手架等,方便开发
openresty 在提供脚手架方面非常有限,请确保环境已安装好
使用项目工程模板 详细查看 tofu-project-default
## 从github中clone
git clone --depth=1 https://github.com/d80x86/tofu-project-default.git new_name
## 进入项目
cd new_name
## 安装tofu framework
./tofu install
## 移除无用的旧git信息
rm -rf .git
## 添加
echo '/lua_modules' >> .gitignore
## 重新添加git信息
git init
./tofu console
如果可以看到类似这样的提示, 恭喜🎉
2021/08/18 19:56:49 [notice] 2482#2482: using the "epoll" event method
2021/08/18 19:56:49 [notice] 2482#2482: openresty/1.19.3.2
2021/08/18 19:56:49 [notice] 2482#2482: built by gcc 9.3.1 20200408 (Red Hat 9.3.1-2) (GCC)
2021/08/18 19:56:49 [notice] 2482#2482: OS: Linux 3.10.0-957.21.3.el7.x86_64
2021/08/18 19:56:49 [notice] 2482#2482: getrlimit(RLIMIT_NOFILE): 100001:1000000
2021/08/18 19:56:49 [notice] 2482#2482: start worker processes
2021/08/18 19:56:49 [notice] 2482#2482: start worker process 2483
2021/08/18 19:56:49 [notice] 2482#2482: start worker process 2484
2021/08/18 19:56:49 [notice] 2482#2482: start privileged agent process 2485
2021-08-18 19:56:49 [notice] tofu version: 0.1.1
2021-08-18 19:56:49 [notice] environment: development
2021-08-18 19:56:49 [notice] listen: 9527
./tofu start
默认使用 9527 端口
可在 conf/config.lua 配置中更改
./tofu stop
开发模式,直接使用control + c 终止
./tofu help
执行后会看到下面的信息
usage:tofu COMMADN [OPTIONS]...
the available commands are:
console start service (development)
help help
install install deps to current resty_module
new create a new project in the directory
reload reload nginx.conf
start start service (production)
stop stop service
version show version information
如想查看某命令的使用方式
./tofu help console
许多时候我们都有比较好的第三方库或解决方案选择,我们直接下载使用即可,无需做过多的重复工作。tofu一些内置组件 view , session 等有第三方依赖,如我们需要使用,只需添加相关组件配置和安装依赖即可使用。安装依赖tofu也提供简单的管理方案,只需要:
-- 在配置文件 tofu.package.lua
-- 添加所需要的opm包或luarocks包
-- 也需要相关的 opm 或 luarocks 的使用环境, 安装方式请查阅资料
-- 依赖列表
-- 使用 -- string 或 {'方式:opm|luarocks', '<package name>'}
deps = {
'bungle/lua-resty-template', -- 默认使用 opm 方式
'bungle/lua-resty-session',
{'luarocks', 'lua-resty-jwt'} -- 指定 luarocks 方式
}
./tofu install
tofu 会根据 tofu.package.lua
文件中所配置 deps={...}
列表使用opm或luarocks进行安装所需要的依赖.
相关依赖安装在当前目录lua_modules
中.(默认情况下tofu已将该目录添加到 lua_package_path 中了)
类信于下面的安装依赖包的过程信息
install bungle/lua-resty-template
* Fetching bungle/lua-resty-template
...
install bungle/lua-resty-session
* Fetching bungle/lua-resty-session
* Fetching openresty/lua-resty-string
...
install lua-resty-jwt
...
- 执行 ./tofu start 或 ./tofu console
- 读取 conf/tofu.nginx.conf 配置文件
- 根据配置初始化相关(生成最终的 nginx.conf 文件, 创建logs目录, 临时目录,缓存等目录)
- 使用 openrsety 启动服务
- 根据环境加载扩展配置文件 conf/extend.lua conf/extend.<环境>.lua
- 实例化和挂载 extend
- 根据环境加载中间件配置文件 conf/middleware.lua conf/middleware.<环境>.lua
- 实例化和挂载 middleware
- 服务启动完成
服务启动完成后会打印下面信息
2021-08-25 13:43:46 [notice] tofu version: 0.1.3.alpha
2021-08-25 13:43:46 [notice] environment: development
2021-08-25 13:43:46 [notice] listen: 9527
- openresty 接收到用户请求调用tofu处理
- tofu 为该请求创建一个独立的 context, 然后调用中间件处理
- 中间件 resty.tofu.middleware.trace 只是一个简单的记录访问时间和响应时间
- 中间件 resty.tofu.middleware.router 路由和uri参数解析处理,处理结果存在 context 中
- 中间件 resty.tofu.middleware.payload 参数解析处理, 结果存在 context 中
- 中间件 resty.tofu.middleware.guard 从context 中获得参数然后调用相应方法处理, 结果中断流程还是继续
- 中间件 resty.tofu.middleware.controller 从context 中获得相应的controller 和 action 处理
注: 所有中间件(middleware) 都是可选的, 这里只列举出较常规的处理流程
框架提供了各种可扩展的配置功能,可以自动合并,按顺序覆盖,且可以根据环境维护不同的配置。
配置文件放在 conf/ 目录下,并根据作用/功能划分到不同的配置文件
文件名 | 作用 | 可选 |
---|---|---|
config.lua | 通用的一些配置,以及tofu.nginx.conf的变量配置 | |
extend.lua | 扩展组件配置,挂载那些扩展到tofu中使用 | |
middleware.lua | 中间件配置, 挂载那些中间件到处理流程中 | |
tofu.nginx.conf | nginx 配置 | |
task.lua | 定时任务配置 | 可选 |
后缀是lua的配置文件是一个可执行的lua文件, 返回文件中的全局变量
-- 配置文件 config.lua 样例
ngx_port = 9527 -- 这是一个在 tofu.nginx.conf 中使用的变量, 配置nginx的server端口
配置文件约定使用一个 与 文件名相同的全局变量名
-- 配置文件 extend.lua 样例
-- 约定一个lua 全局变量名称 extend 与配置文件名称相同, 仅仅是一种约定!
extend = {
--
-- ...
--
}
加载目标配置文件, 加载目标环境配置文件。后加载的会覆盖或合并前面的同名配置。
文件命名格式: [name].[env].lua
, 如 config.development.lua
, config.production.lua
-- 配置文件 config.lua
api = {
appid = '123',
secret = 'asdfghjkl',
}
-- 配置文件 config.production.lua
api = {
secret = 'lkjhgfdsa',
timeout = 5000,
}
配置加载目标配置文件 config.lua
, 然后加载目标环境配置文件 config.production.lua
, 后并后结果
api = {
appid = '123',
secret = 'lkjhgfdsa',
timeout = 5000,
}
更多使用方法查看扩展:
resty.tofu.extend.config
tofu.nginx.conf
这是个特殊的配文件,是个标准的nginx配置文件。 额外支持使用 ${var_name} 变量.
如果需要更复杂的处理,可以在 config.lua
中 设置 ngx_conf_file_template = true
开启 lua-resty-template
语法持持 (需要 库 lua-resty-template ) 支持
context
是处理用户请求过程中的一个对象,贯穿整个请求生命周期。简称ctx
。
与 ngx.ctx
功能类似ngx.ctx
只是要给用户使用的, 而ctx
主要使用于框架内使用。
如主要用于在中间件中流转
tofu 的中间件执行过程是基于洋葱圈模型
return function (options)
return function (ctx, flow)
-- 干 ...
flow() -- 调用后面的中间件
-- ...
end
end
中间件格式为一个高阶阶函数,外部的函数接收一个 options
参数,这样方便中间件提供一些配置信息,用来开启/关闭一些功能。执行后返回另一个函数,这个函数接收 ctx
, flow
参数,其中 ctx
为 context
的简写,是当前请求生命周期的一个对象,flow
是后续的中间件,这样可以很方便的处理后置逻辑。
中间件洋葱圈模型图:
中间件的配置文件在 <应用根目录>/conf/middleware.lua
-- 配置文件 conf/middleware.lua
middleware = {
-- 中间件配置样例
{
enable = true, -- default
match = [[/api]],
handle = 'resty.touf.middleware.trace',
options = {
logger = tofu.log.n
}
},
-- 快速配置方式
'resty.tofu.middleware.router',
-- ...
}
参数 | 类型 | 说明 | 必填 | 缺省值 |
---|---|---|---|---|
handle | string | function | 中间件, 可以是包名 或是中间件的高阶函数 | 是 | |
enable | bool | 控制中间件是否开启,主要用于控制中间件作用于某种环境 | 否 | true |
match | string | function | 设置只有符合某些规则的请求才会进入这个中间件处理 | 否 | |
options | any | 中间件初始化时,传递给中间件函数 | 否 |
string:
包名字符串,如 resty.tofu.middleware.router
,先查找应用middleware目录下,如果没有再按require
方式加载。
-- 配置文件 conf/middleware.lua
middleware = {
-- 方式一
{
handle = 'resty.tofu.middleware.router',
},
-- 方式二
'resty.tofu.middleware.router',
-- 这两种配方式效果是相同的
}
function:
中间件函数, 有些比较简单或临时的中间件,我们可以直接写在配置文件中
-- 配置文件 conf/middleware.lua
middleware = {
-- 中间件配置样例
{
handle = function (options)
return function (ctx, flow)
tofu.log.d('业务处理')
flow()
end
end,
options = { }
},
}
如果我们需要控制中间件在特定的环境中开启或关闭,可以设置enable
参数为 true | false (默认 true)
-- 配置文件 conf/middleware.lua
local _isdev = 'development' == tofu.env
middleware = {
-- 只在开发模式中开启该中间件
{
enable = _isdev,
handle = 'resty.tofu.middleware.trace',
},
}
string:
uri路径正则配置,使用的是ngx.re.match
语法
-- 配置文件 conf/middleware.lua
middleware = {
-- uri 以 /static 前缀开头的请求才进入处理
{
match = [[/static]],
handle = 'xxx_middleware',
},
}
function:
函数返回 true | false 结果来判断匹配
-- 配置文件 conf/middleware.lua
middleware = {
{
handle = 'xxx_middleware',
-- 只有 ios 设备才进入处理
match = function (ctx)
local user_agent = ngx.req.get_headers()['user-agent']
return ngx.re.match(user_agent, [[(iphone|ipad|ipod)]], 'joi')
end
},
}
中间件初始化时,透传给中间件创建函数
-- 配置文件 conf/middleware.lua
middleware = {
{
handle = function (options)
return function (ctx, flow)
end
end,
options = { }, -- 这个参数会原样传入给上面 handle 指定的中间件
},
}
框架默认内置了常用的中间件
中间件名称 | 中间件包名 | 作用 |
---|---|---|
trace | resty.tofu.middleware.trace | 跟踪记录请求 |
router | resty.tofu.middleware.router | 路由解析 |
payload | resty.tofu.middleware.payload | 请求参数解析 |
guard | resty.tofu.middleware.guard | 参数过滤 |
controller | resty.tofu.middleware.controller | 控制器,业务处理 |
根据业务需要添加中间件,按约定放在 lua/middleware
目录下,然后就可以中间件配置中直接使用
创建自定义中间件, 添加文件 lua/middleware/example.lua
-- 文件lua/middleware/example.lua
return function (options)
return function (ctx, flow)
tofu.log.d('你静鸡鸡地来了')
flow()
tofu.log.d('你静鸡鸡地走了')
end
end
然后在中间件配置文件 conf/middleware.lua
中添加配置
-- 配置文件 conf/middleware.lua
middleware = {
-- 方式一
{
handle = 'example',
options = { }
},
-- 方式二
'middleware.example',
}
直接写在配置文件中
-- 配置文件 conf/middleware.lua
middleware = {
-- 自定义中间件
{
handle = function (options)
return function (ctx, flow)
tofu.log.d('业务处理')
flow()
end
end,
options = { }
},
-- ...
}
tofu框架可以说是一个 零 功能的框架,所有功能都是通过扩展得来实现。所以扩展都可以通过配置来控制。
因为openresty已提供了相关基础的很友好和方便调用的api,所以tofu没有必要再封装一套基础的api。
这也极大减少不必要的api和学习的成本。
扩展的配置文件在 <应用根目录>/conf/extend.lua
-- 配置文件 conf/extend.lua
extend = {
-- 扩展配置样例
{
enable = true, -- default
named = 'config',
type = 'default',
default = {
handle = 'resty.touf.extend.config',
options = {
env = tofu.env,
prefix = tofu.ROOT_PATH .. 'conf/',
}
}
},
-- 快速配置方式一
'resty.tofu.extend.builtin',
-- 快速配置方式二, 等同上面方式
{
default = {
handle = 'resty.tofu.extend.builtin', -- 还可以是 function | table
}
},
-- ...
}
参数 | 类型 | 说明 | 必填 | 缺省值 |
---|---|---|---|---|
enable | bool | 控制中间件是否开启,主要用于控制扩展组件作用于某种环境 | 否 | true |
named | string | 为设置扩展命名后可用 tofu. 全局调用 | 否 | |
type | string | 中间件初始化时,指定使用那一组参数初始化扩展组件 |
否 | default |
如果我们需要控制扩展组件在特定的环境中开启或关闭,可以设置enable
参数为 true | false (默认 true)
-- 配置文件 conf/extend.lua
-- 是否开发环境
local _isdev = 'development' == tofu.env
extend = {
-- 只在开发模式中开启该扩展组件
{
named = 'watcher',
enable = _isdev,
default = {
trace = true,
handle = 'resty.tofu.extend.jili'
},
},
}
挂载到tofu
的名称,使得后面可以使用 tofu.<named>
方式调用。命名不能为空字符串,命名不能重复。
当为 nil
时,扩展组件又是 table
,则将扩展中的所有非下划_
开头的属性加载到tofu
指定扩展组件使用那一组参数
-- 配置文件 conf/extend.lua
-- 是否开发环境
local _isdev = 'development' == tofu.env
extend = {
-- 只在开发模式中开启该中间件
{
named = 'log',
-- 根据环境指定不同类型的中间件,分别对应下面的配置
type = _isdev and 'console' or 'file',
-- 把日志打印到终端
console = {
-- ...
},
-- 把日志打印到文件
file = {
-- ...
},
},
}
扩展组件参数格式
-- 配置文件 conf/extend.lua
extend = {
{
named = 'view',
-- 扩展组件参数格式
default = {
handle = 'resty.tofu.extend.view',
options = {
-- ...
}
}
},
}
type指定的扩展组件
参数 | 类型 | 说明 | 必填 | 缺省 |
---|---|---|---|---|
handle | string|function|table | 扩展组件主体内容 | 是 | |
options | any | 透传给扩展组件函数 | 否 |
string:
包名字符串,如 resty.tofu.extend.task
,先查找应用extend目录下,如果没有再按require
方式加载。
-- 配置文件 conf/extend.lua
extend = {
-- 快速配置方式一
'resty.tofu.extend.builtin',
-- 快速配置方式二, 等同上面方式
{
default = {
handle = 'resty.tofu.extend.builtin',
}
},
}
function:
函数, 有些比较简单或临时的扩展组件,我们可以直接写在配置文件中
-- 配置文件 conf/extend.lua
extend = {
{
named = 'bye'
handle = function (options)
return function (who)
tofu.log.d(who, ': 散水,系噉先喇!')
end
end,
options = { }
},
}
-- 在其它地方调用 tofu.bye('肥标')
table:
将handle
所有非下划线_
开头的属性或方法合并(浅copy)到tofu
中
-- 配置文件 conf/extend.lua
extend = {
-- 情况1: 没有命名
{
-- named = nil
handle = {
bye = function (who)
tofu.log.d(who, ': 散水,系噉先喇!')
end,
}
},
-- 在其它地方调用 tofu.bye('肥标')
-- 情况2: 指定命名
{
named = 'foo',
handle = {
-- 如果存在 new 方法则调用
new = function(options)
return {
bye = function (who)
tofu.log.d(who, ': 散水,系噉先喇!')
end,
}
end
}
},
-- 在其它地方调用 tofu.foo.bye('肥标')
}
resty.tofu.middleware.trace
一个简单记录请求的进入中间件时间, 退出中间件时间,请求的uri 和 方法,状态,并使用指定的方法记录/打印
参数 | 类型 | 说明 | 必须 | 缺省 |
---|---|---|---|---|
logger | function | 记录日志的方法 | 否 | ngx.log(INFO) |
-- 配置文件 conf/middleware.lua
middleware = {
{
handle = 'resty.touf.middleware.trace',
options = {
logger = tofu.log.n
}
},
}
resty.tofu.middleware.router
uri路由解析,uri参数解析, 解析结果存放在 context 中
参数 | 类型 | 说明 | 必须 | 缺省 |
---|---|---|---|---|
module | string | 缺省的module 名称 |
是 | default |
controller | string | 缺省的controller 名称 |
是 | index |
action | string | 缺省的action 名称 |
是 | index |
-- 配置文件 conf/middleware.lua
middleware = {
{
handle = 'resty.touf.middleware.router',
options = {
module = 'default',
controller = 'index',
action = 'index'
}
},
}
通过解析 ngx.var.uri 为 [/module]
[/.../path/../]
[/controller]
[/action]
[/arg1/arg2.../...]
[] 中的内容为可选, 如果module, controller, action 各项为空时,则使用配置
- 使用
/
分割 uri - 从左向右查找和检验
module
, 如果没有则使用options.module
配置值 - 在
module
之后,从左向右查找和检验controller
, 如果没有则使用options.controller
配置值 - 在
controller
之后,从左向右查找和检验action
, 如果没有则使用options.action
配置值 - 在
action
之后,从左向右的所有内容解析为 args 数组参数
参数 | 类型 | 说明 | 样例 |
---|---|---|---|
handler | string | 处理品的文件,相对于应用内的 controller/ 目录下 | default/index |
module | string | 模块名称 | default |
controller | string | 控制器名称 | index |
action | string | 方法名称 | index |
args | table | 参数表,数组 | {'arg1', 'arg2'} |
resty.tofu.middleware.payload
解析处理 body 与 uri 参数,get参数合并到 context.args.get, 其它方式合并到 context.args.post, 文件合并到 context.args.file。
参数 | 类型 | 说明 | 必须 | 缺省 |
---|---|---|---|---|
parser | table | 协议解析器表 | 否 |
默认支持
application/x-www-form-urlencoded
application/json
multipart/form-data
-- 配置文件 conf/middleware.lua
middleware = {
'resty.tofu.middleware.payload',
}
function()
return { post={}, get={}, file={} }
end
结果会合并到中间件 context.args 中
-- 配置文件 conf/middleware.lua
middleware = {
{
handle = 'resty.tofu.middleware.payload',
options = {
-- 添加一个 xml 解析器
['application/xml'] = function()
-- 按约定存放到 post | get | file 中
return { post = {} }
end,
-- 重写(覆盖) multipart/form-data 解析器
['multipart/form-data'] = function()
return { post={}, file={} }
end,
-- ...
},
}
}
建议,把解析器写成独文件,通过require引入,这样使得配置文件不至于冗长
resty.tofu.middleware.guard
依赖: resty.tofu.middleware.router
参数过滤校检是一个不可少的环节,将参数统一处理,当参数校验完成后,再进行后续的流程。这样可以减少action 复杂且冗长和重复的代码,使得action更加注重业务方面
参数 | 类型 | 说明 | 必须 | 缺省 |
---|---|---|---|---|
guard_root | string | guard 起始查找目录 | 是 | guard |
suffix | string | 方法后缀,如 action_fuffix | 是 |
-- 配置文件 conf/middleware.lua
middleware = {
'resty.tofu.middleware.guard',
}
使用默认配置基本可以满足需要
当请求 module/controller/action
, 中间件会在 guard 目录下查找 module/controller/action
。
-
没有命中的guard,直接通过,执行后面的中间件流程
-
命中guard中定义了_enter(魔术方法),会优先执行,并判断结果是否为非 false,否则中断流程
-
执行命中guard中定义的action方法,并判断结果是否为非 false
resty.tofu.middleware.controller
依赖: resty.tofu.middleware.router
控制器是处理用户请求业务的主要场所,使得不同的业务对相应的controller.action
简单明了。
controller中间件根据路由中间件resty.tofu.middleware.router的处理结果,在 options.controller_root 配置的目录下开始查找,匹配对应用controller
参数 | 类型 | 说明 | 必须 | 缺省 |
---|---|---|---|---|
suffix | string | 方法action的后缀 | 否 | '' |
logger | function | 方法后缀,如 action_fuffix | 否 | tofu.log.e |
controller_root | string | 控制器查找的根目录 | 否 | controller/ |
controller_allow_underscore | bool | 是否支持下划线_ 开头的控制器 |
否 | false |
action_allow_underscore | bool | 是否支持下划线_ 开头的方法 |
否 | false |
-- 配置文件 conf/middleware.lua
middleware = {
'resty.tofu.middleware.controller',
}
默认情况下controller中间件会从<应用>/lua/controller/ 目录按 module/path/controller.lua 规则查找文件。
我们可以使用 lua module 的方式/风格(推荐)来编写
--
-- 文件 <应用>/lua/controller/api/user.lua
--
local _M = {}
-- 缺省方法 index
function _M.index()
tofu.success()
end
-- 定义方法 info
-- 访问 /api/user/info 该方法将被调用
-- 请求参数
-- id : 用户id
--
function _M.info()
-- 业务样例代码
-- --------------------------
-- 获取参数
local id = tofu.args('id')
-- 从数据库中查询用户信息
local info = tofu.model('user').get({id = id})
-- 以json方式反回结果 {errno:0, errmsg:'', data:{id:, name:, ...}}
tofu.success(info)
end
return _M
使用controller基类是一个很好的方案
-- 按习惯,私有的把它命名为 _base.lua
--
local _M = {}
-- ... 方法 ...
return _M
-- 这个controller 将"继承"上面的 _base
local _M = tofu.extends('_base') -- tofu.extends 提供相对于当前文件目录的查找方式
-- .. 继承 _base 的方法..
return _M
_enter()
当进入controller
时自动调用_leave()
当退出controller
时自动调用
当 controller
的方法明确 return false
时,会中断后面的中间件
resty.tofu.extend.config
使用lua语法作为配置文件,一体化,无需额外学习。借助lua的解析能力,代码即配置,轻松提供强大且可扩展的配置方案。按顺序覆盖,根据环境使用不同配置。
参数 | 类型 | 说明 | 必须 | 缺省 |
---|---|---|---|---|
env | string | 当前环境 | 否 | 当前环境 |
prefix | string | 配置查找目录 | 否 | conf/ |
default | string | 默认配置文件名称 | 否 | config |
-- 配置文件 conf/extend.lua
extend = {
{
named = 'config',
default = {
handle = 'resty.tofu.extend.config',
options = {
env = tofu.env,
prefix = tofu.ROOT_PATH .. 'conf/'
}
}
}
}
配置文件在options.prefix
(默认 <应用>/conf/)目录下添加, 如有 jwt
配置
-- jwt.lua
jwt = {
secret = '5df7f1701b778d03d57456afea567922',
passthrough = true,
}
local jwt = tofu.config.jwt
tofu.log.d( jwt.secret )
tofu.config.jwt
按顺序合并和覆盖 config.lua <-- config.环境.lua <-- jwt.lua <-- jwt.环境.lua
-- 配置文件 config.lua
jwt = {
secret = '5df7f1701b778d03d57456afea567922'
}
-- 配置文件 jwt.lua
jwt = {
secret = 'abc123'
}
-- 配置文件 jwt.production.lua
jwt = {
passthrough = true
}
-------------------------------------------
-- jwt配置的结果
--
jwt = {
secret = 'abc123',
passthrough =
}
resty.tofu.extend.log
提供了 debug, info, notice, warn, error,等多个级别日志,输出方式提供了终端和文件方式
参数 | 类型 | 说明 | 必须 | 缺省 |
---|---|---|---|---|
level | int | 记录日志级别,低于设置级别会被忽略 | 否 | DEBUG |
color | bool | 是否使用颜色,一般在终端环境时使用 | 否 | false |
colors | table | 配色表 | 否 | |
printer | function | 记录器 | 否 | |
fmter | string | 日志格式化模板 | 否 | |
pretty | bool | 是否格式化输出 | 否 | false |
trace | bool | 是否显示更详细信息(文件名,行号等) | 否 | false |
file | string | file日志记录器配置 | 否 | |
rotate | string | file日志记录器配置, 切割间隔。目前只支持'day' | 否 |
-- 配置文件 conf/extend.lua
local _log = require 'resty.tofu.extend.log'
local _isdev = 'development' == tofu.env
extend = {
{
named = 'log',
type = _isdev and 'console' or 'file'
-- 终端日志
console = {
handle = _log.console,
options = {
level = _log.levels.DEBUG,
color = true,
pretty = true,
}
},
-- 文件日志
file = {
handle = _log.file,
options = {
level = _log.levels.INFO,
file = 'logos/tofu.log',
rotate = 'day',
}
}
}
}
tofu.log.d(...)
debug 级别日志
tofu.log.i(...)
info级别日志
tofu.log.n(...)
notice级别日志
tofu.log.w(...)
warn级别日志
tofu.log.e(...)
error级别日志
tofu.log.d('----- debug -----')
tofu.log.i('----- info -----')
tofu.log.n('----- notice -----')
tofu.log.w('----- warn -----')
tofu.log.e('----- error -----')
resty.tofu.extend.session
参数 | 类型 | 说明 | 必须 | 缺省 |
---|---|---|---|---|
name | string | session名称设置 | 否 | tofu_session |
ttl | int | session有效时长(秒) | 否 | 1200 |
renewal | bool | 是否自动续约ttl | 否 | true |
secret | string | session安全码 | 否 | |
storage | string | session的存储方式,默认支持 cookie | shm 等多种方式 | 否 |
-- 配置文件 conf/extend.lua
extend = {
{
named = 'session',
default = {
handle = 'resty.tofu.extend.session',
options = {
ttl = 20 * 60, -- 过期时间(秒)
-- 与 tofu.nginx.conf 中的 set $session_secret 相同
secret = '5df7f1701b778d03d57456afea567922',
-- -- cookie 方式
-- storage = 'cookie'
-- cookie = {}
-- -- shm 方式
storage = 'shm',
shm = {
-- 匹配 tofu.nginx.conf 中的 lua_shared_dict
store = 'tofu_sessions'
}
}
}
}
}
## conf/tofu.nginx.conf
http {
## ...
## 上 conf/extend.lua 中 session 配置的shm store 名称相配
lua_shared_dict tofu_sessions 10m;
## ...
}
tofu.session.get(key)
获取当前session的key的值
local info = tofu.session.get('info')
tofu.session.set(key, value)
设置当前session的key的值 , 并返回旧的值
tofu.session.set('info', {id=1, name='d'})
tofu.session.destroy()
删除当前session的所有值
tofu.session.destroy()
resty.tofu.extend.cache
使用ngx.shared.DICT作为缓存,不支持table, userdata类型
参数 | 类型 | 说明 | 必须 | 缺省 |
---|---|---|---|---|
ttl | int | 缓存时间(秒) | 是 | 5400 |
shm | string | lua_shared_dice名称 | 是 | tofu_cache_dict |
-- conf/extend.lua
extend = {
{
named = 'cache',
default = {
handle = 'resty.tofu.extend.cache',
options = {
ttl = 90 * 60, -- 90 分钟
shm = 'tofu_cache_dict', -- lua_shared_dict 配置的名称
}
}
}
}
tofu.cache.get(key [, init] [, ...])
获得缓存,如果不存在则返回 init, 如果init 是 function,则执行 init(...), 只有值为非nil才会缓存。
初始化时会使用 resty.lock, 所以不会有缓存失效风暴
tofu.cache.set(key, val [, ttl])
设置缓存, val 不支持 table
tofu.cache.del(key)
删除缓存
tofu.cache.incr(key, val [, init] [, ttl])
累加器
resty.tofu.extend.view
依赖: [ lua-resty-template ](bungle/lua-resty-template: Templating Engine (HTML) for Lua and OpenResty. (github.com))
通过开启视图扩展组件,让应用有服务端渲染模板的能力。
参数 | 类型 | 说明 | 必须 | 缺省 |
---|---|---|---|---|
template_root | string | 模板搜索路径 | 否 | view/ |
extname | string | 模板后缀名称 | 否 | .html |
cache | bool | 是否缓存 | 否 | false |
-- conf/extend.lua
extend = {
{
named = 'view',
default = {
handle = 'resty.tofu.extend.view',
options = {
template_root = tofu.ROOT_PATH .. 'view/',
extname = '.html',
cache = true,
}
}
}
}
tofu.view.assign(param [, val])
关联变量到模板中使用
-- 方式一
tofu.view.assign('name', '神探肥标')
tofu.view.assign('id', 123)
-- 方式二, 与上面等效
tofu.view.assign({
name = '神探肥标',
id = 123
})
tofu.view.render(tpl, param)
渲染模板
tofu.view.display(tpl, param)
param 合并 assign所关联变量,并渲染模板到body
-- 方式一
tofu.view.assign('name', '神探肥标')
tofu.view.assign('id', 123)
tofu.view.display()
-- 方式二, 可以使用链式风格
tofu.view
.assign('name', '神探肥标')
.assign('id', 123)
.assign({
pic = '9527'
})
.display()
resty.tofu.extend.model
mysql 是一个较常见也很好用的关系弄数据,在开发中使用SQL操作数据库(CRUD: 增删改查),还是比较麻烦,也容易造成 SQL 注入等安全问题。model 扩展组件提供了快速执行sql,和模型功能。
参数 | 类型 | 说明 | 必须 | 缺省 |
---|---|---|---|---|
default | string | table | function | 指定缺省时的数据库options 配置 | 是 |
数据库配置 options
参数 | 类型 | 说明 | 必须 | 缺省 |
---|---|---|---|---|
host | string | 数据库地址(tcp/ip) | 否 | 127.0.0.1 |
port | int | 数据库端口 | 否 | 3306 |
path | string | 数据库地址(unix socket) | 否 | |
database | string | 数据库名称 | 否 | |
prefix | string | 数据表前缀 | 否 | '' |
user | string | 数据库用户 | 否 | root |
password | string | 数据库密码 | 否 | '' |
charset | string | 连接编码 | 否 | utf8 |
timeout | int | 连接超时(毫秒) | 否 | 5000 |
max_package_size | int | 最大结果集大小 | 否 | 2M |
pool_size | int | 连接池大小(每nginx worker) | 否 | 64 |
logconnect | bool | 是否log连接 | 否 | false |
logsql | bool | 是否log sql 语记 | 否 | false |
-- conf/extend.lua
local _isdev = 'development' == tofu.env
extend = {
{
named = 'model',
default = {
handle = 'resty.tofu.extend.model',
options = {
host = '127.0.0.1',
user = 'root',
password = '',
database = 'test',
logconnect = _isdev,
logsql = _iddev,
}
}
}
}
多数据库配置
-- conf/extend.lua
local _isdev = 'development' == tofu.env
extend = {
{
named = 'model',
default = {
handle = 'resty.tofu.extend.model',
default = 'db1', -- 指定默认使用的数据配置
-- 名为db1的数据库配置
db1 = {
host = '192.168.0.1',
user = 'root',
password = '',
database = 'test',
logconnect = _isdev,
logsql = _iddev,
},
-- 名为db2的数据库配置
db2 = {
host = '192.168.0.2',
user = 'root',
password = '',
database = 'test',
logconnect = _isdev,
logsql = _iddev,
},
-- dbN
-- ...
}
}
}
运行时动态获取,如读写分离
-- conf/extend.lua
local _match = string.match
local _lower = string.lower
extend = {
{
named = 'model',
default = {
handle = 'resty.tofu.extend.model',
-- 样例:根据sql,使用选择数据库配置
default = function (sql)
local op = _match(sql, '^%s-(%a+)')
op = _lower(op)
-- 只读数据库
if 'select' == op then
return {
host = '192.168.0.1',
user = 'root',
password = '',
database = 'test',
logconnect = _isdev,
logsql = _iddev,
}
-- 读写数据库
else
return {
host = '192.168.0.2',
user = 'root',
password = '',
database = 'test',
logconnect = _isdev,
logsql = _iddev,
}
end
}
}
}
tofu.model(name [ , opts])
创建一个 model , 对于单表操作, 这是个利器,可以很方便地完成较复杂的操作。也可以直接执行sql语句。
name
string 通常是表名, 先尝试加载<应用>/lua/model/<name>.lua
,然后创建一个model。
opts
string | table | function 可选参,数据库配置
-- 创建一个model
-- 使用默认配置
local user = tofu.model('user')
-- 使用配置(遵循覆盖原则)
local user = tofu.model('user', { host='127.0.0.1' })
-- 使用名为 db2 的配置
local user = tofu.model('user', 'db2')
-- 使用一个函数的结果作为配置
-- function(sql) -> table
local user = tofu.model('user', function(sql) return { host='127.0.0.1' } end )
model.exec(sql [, opts])
使用 model 执行sql, 提供灵活的占位符操作
sql
string | table
-
string 合法的sql语记
local sql = 'select * from user limit 10' local result = tofu.model().exec(sql)
-
table {sql, param}
{string, ...}, 占位符 ?,安全占位符会根据参数类型,进行转换
local sql = 'select * from user where id=? and name=?' tofu.model().exec({sql, 123, '神探肥标'}) -- 执行的sql: select * from user where id=123 and name='神探肥标'
{string, ...}, 占位符 ??,==不安全占位符,不进行安全转换==
local sql = 'select ?? from user where id=?' tofu.model().exec({sql, 'name', 123}) -- 执行的sql: select name from user where id=123
{string, {...}}, 有名占位符 :name,安全占位符会根据参数类型,进行转换
local sql = 'select * from user where id=:id and name=:name' tofu.model().exec({sql, { id=123, name='神探肥标'}) -- 执行的sql: select * from user where id=123 and name='神探肥标'
{string, ...}, 有名占位符 ::name,==不安全占位符,不进行安全转换==
local sql = 'select ::field from user where id=:id' tofu.model().exec({sql, { field='id, name', id=123}) -- 执行的sql: select id, name from user where id=123
opts
table 可选参,数据库配置
model.get(cond, opts)
获取数据,默认只获取一条记录
cond
string | table
sql 语句 where 后面的内容,支持 ?
| ??
| :name
| ::name
占位符格式。 可参考上面model.exec的样例
-
string
local result = tofu.model('employees').get('emp_id=123') -- 执行的sql: select * from employees where emp_id=123
-
table
-- 占位符 ? local result = tofu.model('employees').get({'emp_id=?', '123'}) -- 执行的sql: select * from employees where emp_id='123' -- 占位符 ?? local result = tofu.model('employees').get({'emp_id=??', '123'}) -- 执行的sql: select * from employees where emp_id=123 -- 有名占位符 :name 与 :name 同理
改变关系逻辑符, 逻辑符使用 cond[1] 控制, ==不作安全转换==,也就是任何内容都会原样插入
-- 默认情况以 and 连接多个条件
tofu.model('employees').get({emp_id=123, name='神探肥标'})
-- 执行的sql: select * from employees where emp_id=123 and name='神探肥标'
-- 使用 or 连接
tofu.model('employees').get({'or', emp_id=123, name='神探肥标'})
-- 执行的sql: select * from employees where emp_id=123 or name='神探肥标'
当值为table即 key = { op, val, ...}, op
为操作符, ==不作安全转换==, >
| <
| =
| between
| in
| not in
等等合法的sql语法都支持。
下面只列举出model中特有的操作符
操作符 | 说明 | 关系 |
---|---|---|
[] | 闭区间 | [a,b] = {x | a ≤ x ≤ b} |
() | 开区间 | (a,b) = {x | a < x < b} |
[) | 半开半闭区间 | [a,b) = {x | a ≤ x < b} |
(] | 半开半闭区间 | (a,b] = {x | a < x ≤ b} |
样例
-- 为减少内容影响,下面model.get调用 opts 参数:
local opts = { limit = false }
-- select * from employees where emp_id < 123
tofu.model('employees').get( {emp_id = {'<', 123} }, opts )
-- select * from employees where emp_id <= 123
tofu.model('employees').get( {emp_id = {'<=', 123} }, opts )
-- select * from employees where emp_id > 123
tofu.model('employees').get( {emp_id = {'>', 123} }, opts )
-- select * from employees where emp_id >= 123
tofu.model('employees').get( {emp_id = {'>=', 123} }, opts )
-- select * from employees where emp_id is null
tofu.model('employees').get( {emp_id = {'is null'} }, opts )
-- select * from employees where emp_id is not null
tofu.model('employees').get( {emp_id = {'is not null'} }, opts )
-- select * from `employees` where `emp_id` between 1 and 20
tofu.model('employees').get( {emp_id = {'between', 1, 20 } }, opts )
-- select * from `employees` where `emp_id` in (1,20)
tofu.model('employees').get( {emp_id = {'in', 1, 20 } }, opts )
-- select * from `employees` where `emp_id` not in (1,20)
tofu.model('employees').get( {emp_id = {'not in', 1, 20 } }, opts )
--
-- 区间操作符
--
-- select * from `employees` where 1 <= `emp_id` and `emp_id` <= 20
tofu.model('employees').get( {emp_id = {'[]', 1, 20 } }, opts )
-- select * from `employees` where 1 < `emp_id` and `emp_id` < 20
tofu.model('employees').get( {emp_id = {'()', 1, 20 } }, opts )
-- select * from `employees` where 1 <= `emp_id` and `emp_id` < 20
tofu.model('employees').get( {emp_id = {'[)', 1, 20 } }, opts )
-- select * from `employees` where 1 < `emp_id` and `emp_id` <= 20
tofu.model('employees').get( {emp_id = {'(]', 1, 20 } }, opts )
opts
table 可选项
参数 | 类型 | 说明 | 缺省 |
---|---|---|---|
filed | string | table | 对应sql中的字段 | * |
limit | number | table | false | 对应sql中的limit, false:关闭limit | 1 |
orderby | string | 对应sql中的 order by | |
forupdate | bool | 对应sql中的 for update | false |
model.set(cond, pair, add)
更新/添加数据
cond
string | table
参考上面model.get的样例。
pair
table
需要更新或添加数据对, kv 结构,k为字段名称要求合乎sql规则,v可以tostring类型, 如果v是table 且table[1] 是 string,会按其值原样处理,==不作安全转换==
add
bool | table
添加模式:将 cond 与 add 合并,进行添加操作, 如果存在则使用 pair 进行更新。所以要求 cond 与 add 的key与value 必须是合乎sql要的
return
-
更新模式: 受影响数量
-
添加模式: 受影响数量, N 添加的id号, 0:更新。(mysql 的 @@innodb_autoinc_lock_mode 设置会影响结果)
-
错误: nil, error
样例
--
-- 更新模式
--
-- sql: update `employees` set `emp_name`='神探肥标' where emp_id = 1
tofu.model('employees').set({emp_id = 1}, {emp_name='神探肥标'})
-- sql: update `employees` set `salary`=salary+1 where emp_id = 1
tofu.model('employees').set({emp_id = 1}, {salary={'salary+1'} })
--
-- 添加模式, 先进行添加,如果存在则更新
--
-- sql: insert into `employees` (`emp_id`,`emp_name`) values (1,'蛋散')
-- on duplicate key update emp_name='神探肥标'
tofu.model('employees').set({emp_id = 1}, {emp_name='神探肥标'}, {emp_name='蛋散'})
model.add(pair [, ...])
添加一条或多条数据
pair
table
数据对 kv 结构,k为字段名称要求合乎sql规则,v可以tostring类型, 如果v是table 且table[1] 是 string,会按其值原样处理,==不作安全转换==.
如果是添加多条,由第一条数据结构决定字段数量
return
-
id
当添加多条数据时,仅是第一条数据的id
-
nil, error
-- sql: insert into `employees` (`emp_name`) values ('神探肥标')
tofu.model('employees').add({emp_name='神探肥标'})
-- sql: insert into `employees` (`emp_name`) values ('神探肥标'),('厨师废柴')
tofu.model('employees').add({emp_name='神探肥标'}, {emp_name='厨师废柴'})
model.del(cond)
删除数据
cond
string | table
参考上面model.get的cond参数。
return
- number 响应条数
- nil, error
-- sql: delete from `employees` where `emp_id`=99
tofu.model('employees').del({emp_id=99})
model.begin([opts])
主动开始事务
opts
数据库配置
model.rollback()
主动事件回滚
model.commit()
主动事务提交
--
local m = tofu.model()
local ok = m.model('employees').add({emp_name = '神探肥标'})
if ok then
m.commit()
else
m.rollback()
end
model.trans(fn [, ...])
事务处理,自动执行 begin commit / rollback
fn
function
业务处理函数
...
any
转递给 fn(...) 函数的参数
return
返回 fn 的返回值
local m = tofu.model()
local result = m.trans( function()
local list = m.model('employees').get(nil, {limit = 10})
if #list < 1 then
return
end
--
-- ... 业务处理
--
local it = list[1]
return m.model('employees').del({emp_id = it.emp_id})
end)
-- 执行的 sql
-- BEGIN
-- select * from `employees` limit 10
-- ...
-- delete from `employees` where `emp_id`=21
-- COMMIT
--
resty.tofu.extend.task
该扩展组件熟于服务组件,提供了三种方式的定时任务
参数 | 类型 | 说明 | 必须 | 缺省 |
---|---|---|---|---|
worker | string | int | 绑定到nginx的worker上执行任务 | 是 | privileged agent |
task | string | table | 任务表或名称, 如果是名称,则在加载conf/名称.lua | 是 | task |
-- 配置文件 conf/extend.lua
extend = {
{
named = 'task',
default = {
handle = 'resty.touf.extend.task',
options = {
worker = 'privileged agent',
task = 'task',
}
}
},
}
参数 | 类型 | 说明 | 必须 | 缺省 |
---|---|---|---|---|
after | int | 延时执行定时器, 在设定的N秒后执行一次任务 | 否 | |
interval | int | 间隔执行定时器,每隔设定的N秒执行一次任务 | 否 | |
cron | string | 计划定时器, 格式 * * * * * 分别代表每 分 时 天 月 周 | 否 | |
handle | string | function | 要执行的函数或包名 | 否 | |
enable | bool | 是否开启该任务 | 否 | true |
immediate | bool | 是否立即执行一次 | 否 | false |
task 配置格式
-- conf/task.lua
task = {
-- 延时执行定时器
{
after = 3,
handle = function()
tofu.log.d('系统启动后,等待3了秒,然后执行了一次')
end
},
-- 间隔执行定时器
{
interval = 5,
handle = function()
tofu.log.d('系统启动后,每隔5了秒,执行了一次')
end
},
-- 计划执行定时器
{
cron = '0 3 * * *', -- 每天在03:00时执行计划任务task.clean
handle = 'task.clean'
}
}
resty.tofu.extend.builtin
内置扩展api, 直接使用 tofu. 方式使用
-- 配置文件 conf/extend.lua
extend = {
'resty.tofu.extend.builtin'
}
tofu.response(errno, errmsg, data)
响应请求,并默认以 Content-Type = application/json 方式回复
errno
状态号
errmsg
错误信息
data
有效数据
tofu.response(0, '正确', {name='tofu(豆腐)'})
-- 响应body
-- {"errno":0, "errmsg"="正确", "data":{"name":"tofu(豆腐)"}}
errno, errmsg, data 字段名称,可以在 conf/config.lua 中配置
state_field
message_field
data_field
tofu.success(data, msg)
使用 default_state 响应请求(调用 tofu.response)
tofu.success()
-- 响应body
-- {"errno":0, "errmsg":""}
tofu.fail(errno, errmsg, data)
响应错误请求(调用 tofu.response)
tofu.fail(-1, "未知错误")
-- 响应body
-- {"errno":-1, "errmsg":"未知错误"}
resty.tofu.extend.jili
依赖:系统 inotify 库
在开发过程中我们常常在修改->重启->出错->修改->重启,不断地重复。jili(吉利)组件是个文件监控组件,当检测到项目中的文件有改动,自动按规则进行重启或重新加载
参数 | 类型 | 说明 | 必须 | 缺省 |
---|---|---|---|---|
trace | bool | 是否显示控加重新加载文件时的一些信息 | 否 | true |
-- 配置文件 conf/extend.lua
extend = {
{
named = 'watcher',
default = {
handle = 'resty.touf.extend.jili',
options = {
trace = true
}
}
},
}
在tofu框架中使用websocket 非常简单,只需把普通的 controller "升级" 为wscontroller即可
local _wsc = require 'resty.tofu.wscontroller'
upgrade(handler [, opts])
handler
table, 事件接收处理器
事件方法 | 说明 | 必须 | 返回值 |
---|---|---|---|
_open(wb) | 有新的连接 | 否 | state |
_close(wb , state) | 连接断开 | 否 | |
_data(wb, data , state) | 有消息进入 | 否 | |
_ping(wb, data, state) | 收到ping消息 | 否 | |
_pong(wb, data, state) | 收到pong消息 | 否 | |
_timeout(wb, state) | 超时 | 否 |
wb
lua-resty-websocket 对象
state
保存当前用户状态,连接关闭后会被丢弃
data
文本消息或binary消息
opts
table, 配置
参数 | 说明 | 必须 | 缺省 |
---|---|---|---|
timeout | 设置超时(秒) | 否 | 5000 |
max_payload_len | 最大消息长度(byte) | 否 | 65535 |
样例, ws://xxxx/ws 或 ws://xxxx/default/ws/index 进行连接
-- lua/controller/default/ws
local _wsc = require 'resty.tofu.wscontroller'
local _M = {}
function _M.index()
-- webscoket 握手处理,并设置接收websocket事件的处理
_wsc.upgrade(_M)
end
--
-- websocket 事件处理
--
-- 当有连接完成时
function _M._open(wb)
tofu.log.d('用户连接')
local state = {}
rturn state
end
-- 当连接断开时
function _M._close(web, state)
tofu.log.d('断开连接')
end
-- 当有消息时
function _M._data(wb, data, state)
tofu.log.d('接收数据:', data)
wb:send_text(data)
end
return _M
tofu框架提供了一个简单的参数验证器 resty.tofu.validation
。在tofu框架中推荐在guard阶断中使用,可以有效地验证参数,拦截请求,使得业务处理更新干净。
local validation = require 'tofu.validation'
options(opts)
设置
-
opts
参数 类型 说明 缺省 mode int 0:检测所有参数,1:当无效参数时立即返回(中断后面的参数检测流程) 0 trim bool 是否去两端空白 false amend bool 是否自动修正参数(需指定method), 如bool参可以是 true | 1 'true' false errmsg string | table 缺省错误信息 优先级 指定>sgring>[0] errmsg
参数 类型 说明 缺省 [0] string 特殊的, 缺省信息,当没有指定错误信息时,使用该信息 参数错误 required string required参数的错误信息 参数不参为空
handle(rules)
参数验证
-
rules
参数校验列表
校验方法名 说明 区间{min, max} int 整型 支持 float 数值(number) 支持 digit 纯数字串 支持 string 字符串 支持 date 日期 bool 布尔 alpha 字母 hex 十六进制 alphanumeric 字母数字 样例
-- <应用>lua/guard/default/index.lua local _validator = require 'resty.tofu.validation'.handle local _M = {} function _M.index() -- 验证用户帐号与用户密码 local rules = { -- 参数名称 = { 验证规则 } -- 参数为必填, 字符串,长度限定在 [2, 20] 区间 account = {required=true, string={min=2, max=20}, errmsg='帐号参数错误'}, -- 参数为必填, 字符串,长度限定在 [6, 20] 区间 password = {required=true, string={min=6, 20}, errmsg={ string='密码长度错误' }} } -- 如果参数有错误,则返回false 中断流程 local ok, errs = _validator(rules) if not ok then tofu.log.d(errs) return tofu.fail(400, errs) end end return _M
register(fs, f)
注册新的/自定义验证器
-
fs
string 过滤器名称, table {string = function} 添加多个型式
保留的方法/属性
名称 说明 缺省 required 是否必填参 false errmsg 错误信息 参数错误 value 获取值 default 缺省值 trim 是否去两端空白 false amend 是否自动修 false method 参数使用那种方法获取 -
f
function(value, cond) -> bool 验证器
value
将要校验的值cond
校验条件当 value 合乎 cond 时返回 true
样例
-- 增一个名为 mylist 的验证器
-- 作用:值必须存在列表中
local register = require 'tofu.validation'.register
register('mylist', function(v, cond)
if 'table' == type(cond) then
for _, c in ipairs(cond) do
if v == c then return true end
end
return false
end
return false
end)
这个验证器有什么用,怎么用
-- <应用>/lua/guard/default/test.lua
-- 有参数要求
-- y 年份, 必填,且只可以是 2019 2020 2021 的其中之一
-- m 月份, 可选,且只可以是 02 05 07 12 这四个月份的其中之一, 缺省为 07
local _validator = require 'resty.tofu.validation'.handle
local _M = {}
function _M.date()
local rules = {
-- 参数 y
y = { required = true, mylist = {'2019', '2020', '2021'}, errmsg='年份参数错误'},
-- 参数 m
m = { mylist = {'02', '05', '07', '08'}, default='07', errmsg = '月份参数错误'}
}
local ok, err = _validator(rules)
if not ok then
return tofu.fail(400, err)
end
end
return _M
可以看到我们的验证器是可以通过简单的叠加,达到处理复杂的参数验证
该包的api可以独立使用,也可以使用扩展组件方式集成到tofu中
local _util = require 'resty.tofu.util'
split(str, delimiter)
使有分隔符切分字符串,返回table(数组)
-
str
要处理的字符串 -
delimiter
分隔符号local str = '蛋散,粉肠,茂梨,碌葛' local ret = _util.split(str, ',') -- 结果: ret = {'蛋散‘,’粉肠‘,’茂梨‘,’碌葛'}
* return table
msplit(str, sep)
使有分隔符切分字符串,返回table(数组)。与split不同,该方法支持多个分隔符
-
str
要处理的字符串 -
sep
分隔符号local str = '蛋散,粉肠,茂梨,碌葛|神探肥标|厨师废柴' local ret = _util.msplit(str, ',|') -- 结果: ret = {'蛋散‘,’粉肠‘,’茂梨‘,’碌葛',神探肥标','厨师废柴'}
-
return string
dirname(str)
getpath(str)
切分路径,这两个函数作用一样
-
str
local path = '/home/d/tofu/test.lua' local ret = _util.getpath(str) -- 结果: ret = '/home/d/tofu/'
-
return string
trim(str)
去字符串两端空白
-
str
要处理的字符串local str = ' 神探肥标 厨师废柴 ' local ret = _util.trim(str) --结果: ret = '神探肥标 厨师废柴'
-
return string
bin_hex(s)
把目标字符串,以二进制方式转换为十六进制字符串
s
要处理的字符串
hex_bin(s)
把十六进制字符串,以二进制方式转换为字符串
-
s
要处理的字符串local str = 'hi tofu!' _util.hex_bin(str) -- 686920746f667521 _util.bin_hex('686920746f667521') -- hi tofu!
-
return string
envsubstr(str, env)
字符串模板中的变量${}
替换
-
str
字符串模板 -
env
变量表local str = 'hi ${name}!' local ret = _util.envsubstr(str, { name='tofu' }) -- 结果: ret = 'hi tofu!'
-
return string
getusec()
获取系统微秒级时间戳
- return int
gettimezone()
获取当前时区
- return int
tosecond(str, tz)
日期时间转换时间戳(秒)
-
str
支持 yyyy-mm-dd hh:ii:ss | yyyy/m/d | yyyy-m-d | hh:ii:ss 格式的日期时间字符串 -
tz
时区 default: 0,如北京时间东8区_util.tosecond('1970-01-01 08:00:00') -- 28800 秒 _util.tosecond('1970-01-01 08:00:00', 8) -- 这是个北京时间字符串, 返回 0 秒
-
return int
isempty(obj)
是否为空 '', {}, nil, ngx.null
- return bool
isint(v)
是否是int型
- return bool
isfloat(v)
是否是 float 型
- return bool
iscallable(f)
是否可调用
- return bool