• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    迪恩网络公众号

horan-geeker/nana: Lua http api framework

原作者: [db:作者] 来自: 网络 收藏 邀请

开源软件名称(OpenSource Name):

horan-geeker/nana

开源软件地址(OpenSource Url):

https://github.com/horan-geeker/nana

开源编程语言(OpenSource Language):

Lua 100.0%

开源软件介绍(OpenSource Introduction):

Nana

GitHub release license

English Document

openresty 是一个为高并发设计的异步非阻塞架构,而 nana 是基于 openrestyapi 框架。

目录

====

快速上手

routes.lua

route:get('/index', 'index_controller', 'index')

controllers/index_controller.lua

local response = require("lib.response")

local _M = {}

function _M:index(request)
    return response:json(0, 'request args', request.params) -- return response 200 and parse request params to json output
end

return _M

访问结果

curl https://api.lua-china.com/index?id=1&foo=bar

{
    "msg": "request args",
    "status": 0,
    "data": {
        "foo": "bar",
        "id": "1"
    }
}

压力测试

绑定一个 CPU

wrk -t1 -c 100 -d10s http://localhost:60000/

Running 10s test @ http://localhost:60000/
  1 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     3.70ms    4.23ms  29.84ms   82.74%
    Req/Sec    43.31k     2.63k   48.61k    82.00%
  431043 requests in 10.02s, 97.01MB read
Requests/sec:  43024.54
Transfer/sec:      9.68MB

内存基本没有变化,单 CPU 打满

对比 lor 框架

wrk -t1 -c 100 -d10s http://localhost:60004/hello

Running 10s test @ http://localhost:60004/hello
  1 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     5.01ms  621.83us  14.66ms   92.35%
    Req/Sec    20.02k     0.96k   21.35k    78.00%
  199275 requests in 10.01s, 46.94MB read
Requests/sec:  19898.67
Transfer/sec:      4.69MB

对比 golang gin 框架

wrk -t1 -c 100 -d10s http://localhost:60002/ping

Running 10s test @ http://localhost:60002/ping
  1 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     8.05ms   10.04ms  78.14ms   85.71%
    Req/Sec    20.39k     3.19k   26.53k    68.00%
  203091 requests in 10.02s, 27.31MB read
Requests/sec:  20260.14
Transfer/sec:      2.72MB

安装

手动安装

  • git clone https://github.com/horan-geeker/nana.git
  • 执行 cp env.example.lua env.lua
  • 配置 nginx,配置 lua_package_path '/path/to/nana/?.lua;;'; 指向 nana 的根目录, content_by_lua_file 指到框架的入口文件 /path/to/nana/bootstrap.lua

文档

项目配置

  • 项目的配置文件主要放在 config/app.lua
  • 数据库配置文件 config/database.lua
  • 接口状态码的配置文件 config/status.lua

路由

路由配置文件在项目根目录 routes.lua,如使用POST请求访问 http://your-app.test/login 时,会交给 auth_controller 下的 login(request) 函数来处理,并且会将 request 注入 login 方法:

route:post('/login', 'auth_controller', 'login')

框架内核使用 trie 字典树实现路由结构,算法复杂度 O(1),可以高效的匹配路由

支持 http 请求类型

  • GET
  • POST
  • PATCH
  • PUT
  • DELETE
  • HEAD

路由参数

当需要使用 url 来获取参数时,你可以这样写路由配置,id 会传到对应的 show() 方法里

route:get('/users/{id}', 'user_controller', 'show')

多个参数

使用花括号来代表传递的参数,如:

route:get("/users/{user_id}/comments/{comment_id}", 'user_controller', 'comments')

可匹配/users/1/comments/2,在comments action里,直接写上两个参数即可,命名不进行约束

function _M:comments(user_id, comment_id)
    ...
end

路由群组

路由群组主要用来定义一群 uri 的公共属性,目前支持群组中间件,比如下边需要在 注销重置密码 的时候验证用户需要处于登录态,利用路由中间件只需要在中间件进行鉴权,这里会在调用 controller 之前先调用 middleware > authenticate.luahandle() 方法来对用户进行鉴权,这样就不用在每个 controller 的方法里都进行鉴权操作了:

route:group({
        middleware = {'authenticate'},
    }, function()
        route:post('/logout', 'auth_controller', 'logout') -- route:http_method(uri, controller, action)
        route:post('/reset-password', 'user_controller', 'resetPassword')
    end)

中间件

中间件都需要写在 middleware 文件夹下,并且需要写上命名为 handle() 的方法,中间件 的设计模式解决了代码的复用,我们可以在中间件中自定义自己的逻辑,如middleware > authenticate.lua

function _M:handle()
    if not auth_service:check() then
        return false, response:json(4,'no authorized in authenticate')
    end
end

当返回 false 的时候会直接返回 return 的第二个参数,进而不再执行 controller 的内容,当返回 true 的时候继续执行,你可以把你自定义的中间件写到 middleware 的文件夹下, 该文件夹下已有了一个示例中间件 example_middleware.lua

控制器

在路由匹配的uri,第二个参数就是控制器的路径,默认都是在controllers文件夹下的文件名称,第三个参数是对应该文件的方法,可在方法中返回 response 响应。

Request

local request = require("lib.request")

  • request.params 请求参数
  • request.headers 请求头集合
  • request.method 请求方法类型
  • request.uri 请求 uri
local request = require("lib.request")
local args = request:all() -- get all params,not only uri args but also post json body
args.username -- get username prop

参数获取

local request = require("lib.request")
local args = request:all() -- 拿到所有参数,同时支持 get post 以及其他 http 请求
args.username -- 拿到username参数

Response

框架使用的 lib/response.lua 中的 json 方法通过定义数字来代表不同的response类型,该方法支持四个参数

  1. 第一个参数是状态码,16进制状态码对应 config/status.lua
  2. 第二个参数是错误码文案,默认值是根据第一个参数对应 config/status.lua 中的文案
  3. 第三个参数是需要向前端返回的数据,可省略
  4. 第四个参数是返回的 http 状态码,可省略,默认是200
return response:json(0x000000, 'success message', data, 200)
--[[
{
    "msg": "success message",
    "status": 0,
    "data": {}
}
--]]

或者返回错误信息

return response:json(0x000001)
--[[
{
    "msg": "验证错误",
    "status": 1,
    "data": {}
}
--]]

当然你可以在 config > status.lua 中可以增加返回状态码

定义 response json 协议

config 目录下的 status.lua 定义了返回的 statusmsg 内容,默认返回的格式是 {"status":0,"message":"ok","data":{}} 你可以通过修改 lib/response.luajson 方法来自定义不同的结构

验证数据

local validator = require('lib.validator')
local request = require("lib.request")
local args = request:all() -- 拿到所有参数
local ok,msg = validator:check(args, {
    name = {max=6,min=4}, -- 验证 name 参数长度为4-6位
    'password', -- 验证 password 参数需要携带
    id = {included={1,2,3}} -- 验证 id 参数需要携带且是 1, 2, 3 中的某一个
    })

Cookie

lib/helpers.lua 中包含了 cookie 的辅助方法

helpers.set_cookie(key, value, expire) -- expire 是可选参数,单位是时间戳,精确到秒
helpers.get_cookie(key)

数据库操作 ORM

默认的数据库操作都使用了 ngx.quote_sql_str 处理了 sql注入问题

-- 在 models 文件夹里,定义一个表名为 users 的模型, user.lua
local Model = require("lib.model")
local User = Model:new('users')
return User

检索

-- 拿到 users 表 `id` 为 1 的用户
local user = User:find(1)

-- 获取表中所有数据
local users = User:all()

-- 返回 users 表中 username 字段的值是 `cgreen` 的,`password` 字段的值是 `xxxxxx` 的多条数据,注意此处返回是 table 数组,`first()` 方法返回的是单条数据
local user = User:where('username','=','cgreen'):where('password','=','xxxxxxx'):get()

-- 返回 `name` 为 `xxx` 或者 `yyy` 的所有用户 table 数组
local users = User:where('name','=','xxx'):orwhere('name','=','yyy'):get()

新增

-- 创建一个用户
User:create({
    id=3,
    password='xxxxxx',
    name='hejunwei',
    email='heunweimake@gmail.com',
})

更新

-- 更新 id = 1 的 user 的 name 为 test, avatar 为� NULL
local ok, err = User:where('id', '=', 1):update({
        name='test',
        avatar='null'
  })
if not ok then
    ngx.log(ngx.ERR, err)
end

删除

-- 删除 id = 1 的用户
local ok, err = User:where('id','=','1'):delete()
if not ok then
    ngx.log(ngx.ERR, err)
end

-- 软删除
local ok, err = User:where('id','=','1'):soft_delete()
if not ok then
    ngx.log(ngx.ERR, err)
end

软删除将 deleted_at 字段置为当前时间,字段名在 models/model.lua 中配置

排序

orderby(column, option)方法,第一个参数传入排序的列名,第二个参数默认为ASC 也可以传入 ASC 正序 或 DESC 倒序(不区分大小写),Post:orderby('created_at'):get()

分页

local userPages = User:paginate(1)
-- 返回如下结构:
{
    "prev_page": null,
    "total": 64,
    "data": [
        {user1_obj},
        {user2_obj},
        ...
    ],
    "next_page": 2
}

当不存在上一页(下一页)时,prev_pagenext_page)为 null

使用原生 sql

使用原生 sql 时需要注意自己去处理 sql 注入
local Database = require('lib.database')

  • local res, err = Database:mysql_query(sql) -- 执行 SQL 返回结果集,注意读操作(SELECT)返回的是 结果集,写操作(INSERT,UPDATE,DELETE)返回 受影响的行

模型间关系

目前只支持一层关系,单个模型进行关联,之后会进行完善,该方法只是对开发友好,完全可以用 where 条件限定替代

一对多

以 user 关联 post 为例,在 user 模型中定义关系 has_many,参数:

  1. 关联模型
  2. 外表 id
  3. 本表 id
-- user.lua
local Model = require("models.model")
local Post = require('models.post')
local User = Model:new('users')
function User:posts()
    return User:has_many(Post, 'user_id', 'id')
end
return User

-- post.lua
local Model = require("models.model")
local Post = Model:new('posts')
return Post

-- controller 调用
local user_and_post = User:where('id', '=', user_id):with('posts'):get()
--[[
[
    {
        "name":"horan",
        // 返回值会带上 post 为 key 的对象
        "posts":{
            "id":67,
            "user_id":1,
            "title":"article title",
            "content":"article content"
        },
        "email":"hejunweimake@gmail.com"
    }
]
--]]

多对一

如一个用户可以拥有多篇文章,以 post 关联 user,在 post 模型中定义关系 belongs_to,参数:

  1. 关联模型
  2. 外表 id
  3. 本表 id
-- post.lua
local Model = require("models.model")
local User = require('models.user')
local Post = Model:new('posts')

function Post:user()
    return Post:belongs_to(User, 'id', 'user_id')
end

return Post

-- user.lua
local Model = require("models.model")
local User = Model:new('users')
return User

-- controller 调用
local posts_with_user = Post:where('id', '=', 1):with('user'):first()
--[[
{
    "id":1,
    "user_id":1,
    "title":"article title",
    "user":{
        "id":1,
        "name":"openresty"
    },
    "content":"article content"
}
--]]

读写分离

通过配置 config/database.lua 文件中 mysql.READmysql.WRITE 框架会根据 model 的操作自动分配读写,如果不做分离则配置为相同的

由于主从同步是异步的,业务中先写后读的话,默认都会去主库查询,保证数据写入后能立即查询 目前只支持配置一主一从,如有多个从库可以使用四层代理 IP 来解决

上一篇:
shdown/luastatus: universal status bar content generator发布时间:2022-08-16
下一篇:
ldb/lua-telegram-bot: Lua Library for the Telegram Bot API发布时间:2022-08-16
热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap