Need help with luastar?
Click the “chat” button below for chat support from the developer who created it, or find similar developers for support.

About the developer

luastar
126 Stars 51 Forks 52 Commits 0 Opened issues

Description

一个基于openresty的http接口和web开发框架

Services available

!
?

Need anything else?

Contributors list

luastar

1. luastar简介

1.1 luastar是一个基于openresty的高性能高并发高效率http接口和web网站开发框架

1.2 luastar在macOS和CentOS6.5+系统,openresty-1.7.10.2+环境测试通过。

1.3 luastar主要特性:

  • request/response封装
  • 缓存管理
  • 配置文件管理
  • 路由/访问频次/拦截器配置
  • 类似 spring bean 服务管理
  • mysql和redis访问封装
  • httpclient等常用工具封装
  • web系统支持

2. luastar安装

2.1 openresty 安装

请参考官网介绍,https://openresty.org/cn/installation.html

建议安装目录:/usr/local/openresty

2.2 luastar 安装

2.2.1 下载luastar

从github下载luastar到本地目录,例如:/data/apps/luastar下。

2.2.2 修改luastar配置

替换配置文件『/yourpath/luastar/conf/luastar*.conf』中的openresty安装路径和luastar存放路径,如下:

## 该配置文件最好放到openresty/nginx/conf/**/下统一进行管理
## 设置lua包路径(';;'是默认路径,?.dylib是macos上的库,?.so是centos上的库)
lua_package_path '/Users/zhuminghua/Documents/work-private/luastar/luastar/libs/?.lua;/Users/zhuminghua/Documents/work-private/luastar/luastar/src/?.lua;;';
lua_package_cpath '/Users/zhuminghua/Documents/work-private/luastar/luastar/libs/?.dylib;/Users/zhuminghua/Documents/work-private/luastar/luastar/libs/?.so;;';

luastar初始化

init_by_lua_file '/Users/zhuminghua/Documents/work-private/luastar/luastar/src/luastar_init.lua';

设置成一样避免获取request_body时可能会缓存到临时文件

#client_max_body_size 50m; #client_body_buffer_size 50m;

请求频次限制字典

lua_shared_dict dict_limit_req 100m; lua_shared_dict dict_limit_count 100m;

server { listen 8001;

关闭后不用重启nginx即可访问最新代码,生产环境一定要置为on(默认值)

#lua_code_cache off; server_name localhost;

luastar路径

set $LUASTAR_PATH '/Users/zhuminghua/Documents/work-private/luastar/luastar';

应用名称

set $APP_NAME 'demo';

应用路径

set $APP_PATH '/Users/zhuminghua/Documents/work-private/luastar/demo';

应用使用的配置,可区分开发/生产环境,默认使用app.lua

set $APP_CONFIG '/config/app_dev.lua';

访问日志

access_log '/Users/zhuminghua/logs/nginx/demo/access.log' main;

错误/输出日志

error_log '/Users/zhuminghua/logs/nginx/demo/error.log' info; location / { default_type text/html; content_by_lua_file '${LUASTAR_PATH}/src/luastar_content.lua'; } }

server { listen 8002;

web项目关闭lua_code_cache后session会失效

#lua_code_cache off; server_name localhost;

luastar路径

set $LUASTAR_PATH '/Users/zhuminghua/Documents/work-private/luastar/luastar';

应用名称

set $APP_NAME 'demo2';

应用路径

set $APP_PATH '/Users/zhuminghua/Documents/work-private/luastar/demo2';

应用使用的配置,可区分开发/生产环境,默认使用app.lua

set $APP_CONFIG '/config/app_dev.lua';

template模板跟路径,web项目需要配置

set $template_root '/Users/zhuminghua/Documents/work-private/luastar/demo2/views';

访问日志

access_log '/Users/zhuminghua/logs/nginx/demo2/access.log' main;

错误/输出日志

error_log '/Users/zhuminghua/logs/nginx/demo2/error.log' info; location / { default_type text/html; content_by_lua_file '${LUASTAR_PATH}/src/luastar_content.lua'; }

静态文件目录(.js,.css...)

location /assets { root '/Users/zhuminghua/Documents/work-private/luastar/demo2'; index index.html index.htm; } }

luastar/conf/目录下多个文件分别对应不同环境,例如luastar_dev.conf是开发环境的配置,luastar.conf是生产环境的配置

2.2.3 修改nginx配置

修改openresty/nginx/conf/nginx.conf,引入luastar项目配置文件:

include /Users/zhuminghua/Documents/work-private/luastar/luastar/conf/luastar_dev.conf;

2.2.4 启动nignx

/usr/local/openresty/nginx/sbin/nginx -c openresty/nginx/conf/nginx.conf

2.3 测试访问

http://localhost:8001/api/test/hello http://localhost:8001/api/test/hello?name=haha

3 api开发

3.1 luastar 项目结构

luastar //luastar项目
    conf        //可移到openresty/nginx/conf/下
    libs        //第三方库
    src     //luastar源码
demo    //api项目
    config  //配置目录
        app*.lua        //配置文件
        bean.lua        //bean配置
        msg.lua     //文案配置
        route.lua       //路由/频次控制/拦截器配置
    src     // 源码目录
        com
            luastar
                demo    //包
                    ctrl            //控制类目录
                    interceptor //拦截器
                    service     //服务类
                    util            //辅助类
demo2   //web项目
    config  //配置目录
    src     //源码目录
    views   //template视图目录
    assets  //静态文件目录

3.2 luastar 全局变量

luastar 在初始化时,定义了几个常用的全局变量,在项目中可以直接使用,不用require引入,详情请参看:luastar/src/luastar_init.lua

|全局变量 | 说明 | | :--- | :--- | | Class | luastar中的类定义 | | cjson | json工具类 | | _ | moses工具类(luastar修改过) | | template | html模板类 | | luastarcache | luastar缓存 | | luastarconfig | luastar配置 | | luastar_context | luastar上下文 | | logger | 日志辅助 | | session | web session |

3.3 缓存

luastar提供了lua内存缓存,根据openresty机制,每个worker存有一份,所以在使用缓存前,需要先判断是否存在(即使初始化存储过),luastar中使用缓存存储了配置文件信息、bean信息、路由和拦截器信息等等。 例如:luastar/src/core/config.lua

local _M = {}

local util_file = require("luastar.util.file")

function _M.getConfig(k, default_v) -- 从缓存中获取配置信息 local app_config = luastar_cache.get("app_config") if app_config then -- 如果配置信息存在,返回 return app_config[k] or default_v end -- 如果配置信息不存在,初始化 ngx.log(ngx.INFO, "init app config.") -- 加载配置文件 local app_config_file = ngx.var.APP_CONFIG or "/config/app.lua" local config_file = ngx.var.APP_PATH .. app_config_file app_config = util_file.loadlua_nested(config_file) or {} -- 缓存配置信息 luastar_cache.set("app_config", app_config) -- 返回结果 return app_config[k] or default_v end

return _M

说明:内存缓存的好处在于支持所有的lua结构,没有限制。

如果需要缓存的内容比较简单或者可以序列化成json,可以考虑使用ngx.shared.DICT,实现全局共享。

3.4 luastar_context 上下文

有些内容在initbylua阶段无法初始化,需要延后在contentbylua阶段执行,不能放到初始化阶段里作为全局变量直接使用,所以放到了上下文中,详见:luastar/src/luastar/core/context.lua

3.4.1 初始化项目包路径和获取路由

初始化项目包路径和获取路由已经在请问入口类中调用了,实际项目中应该不会调用,详见:luastar/src/luastar/luastar_content.lua

3.4.2 获取beanFactory

beanFactory是参考spring中的bean管理实现的一套lua bean,用于服务层,和require进来的对象相比,最大的区别是lua bean是用类实例化出来的对象,可以是单例的,也可以是多实例的,有自己的属性和方法。

-- 获取mysql服务
local beanFactory = luastar_context.getBeanFactory()
local mysql_util = beanFactory:getBean("mysql")
-- 创建链接
local mysql = mysql_util:getConnect()
-- 执行sql
local res, err, errno, sqlstate = mysql:query("select * from user")
ngx.log(logger.i(cjson.encode({
    sql = sql,
    res = res,
    err = err,
    errno = errno,
    sqlstate = sqlstate
})))
-- 关闭链接
mysql_util:close(mysql)

3.4.3 获取msg

项目中可能需要将输出的文案配置到配置文件中,便于做国际化或替换。 例如:demo/conf/msg.lua

--[[
提示消息配置
普通消息
local message = luastar_context.getMsg("msg_live", "100001")
占位直接使用string的格式化方法,例如%s, %d等
local message = luastar_context.getMsg("msg_live", "100002"):format(100.00)
多级配置消息获取方法
local message = luastar_context.getMsg("msg_live", "100003", "001")
--]]
msg_pub = {
    ["100001"] = "错误1!", --
    ["100002"] = "金额不能超过%d元!", --
    ["100003"] = {
        ["001"] = "错误3-1!", --
        ["002"] = "错误3-2"
    }, --
    ["199999"] = nil
}
msg_uc = {
    ["200001"] = "错误1!", --
    ["200002"] = "错误2!", --
    ["200003"] = "错误3!", --
    ["299999"] = nil
}

3.5 调试与日志

3.5.1 调试

luastar/openresty可以利用ZeroBraneStudio工具调试。

openresty使用ZeroBraneStudio调试步骤可参考链接:http://notebook.kulchenko.com/zerobrane/debugging-openresty-nginx-lua-scripts-with-zerobrane-studio

luastar使用ZeroBranStudio调试步骤如下:

1、在包路径中增加ZeroBranStudio相关库文件,注意macos使用.dylib,centos上使用.so库

lua_package_path 'luastar其他库;/Applications/ZeroBraneStudio.app/Contents/ZeroBraneStudio/lualibs/?/?.lua;/Applications/ZeroBraneStudio.app/Contents/ZeroBraneStudio/lualibs/?.lua;;';
lua_package_cpath 'luastar其他库;/Applications/ZeroBraneStudio.app/Contents/ZeroBraneStudio/bin/clibs/?.dylib;;';

2、在需要调试的代码前后加上

require('mobdebug').start("127.0.0.1")
-- 调试代码
require('mobdebug').done()

3、断点,按ZeroBranStudio方法启动调试

3.5.2 日志

如果觉得调试起来麻烦,日志就是最好的调试办法,简单高效(熟练后完全可以不需要调试)。

luastar直接使用ngx.log输出,之前也有用过第三方库 https://github.com/Neopallium/lualogging 在多worker模式中容易造成日志丢失。ngx.log的缺点是不能个性化按天输出(可以用脚本定时分割),输出大小有限制,不过一般也够用了。

luastar只是简单封装了固定输出request_id和简化的方法,不包装起来是为了直观的输出日志的位置

ngx.log(logger.info("name=", name))
-- 或者
ngx.log(logger.i("name=", name))

输出结果:

2016/12/19 17:01:50 [info] 14545#0: *553 [lua] hello.lua:12: --[2y6hNDFGd4Nxi7FE9UAP]--name=world, try to give a param with name., client: 127.0.0.1, server: localhost, request: "GET /api/test/hello HTTP/1.1", host: "localhost:8001"

[2y6hNDFGd4Nxi7FE9UAP]是本次请求的request_id,便于在日志量大的情况下定位一次请求的所有日志。

3.6 项目配置

一般项目都会有配置文件,在luastar项目中,配置文件放在demo/config/目录下,可以通过在luastar.conf文件中指定不同环境的配置,默认使用app.lua文件

server {
    listen 8001;
    ...
    set $APP_CONFIG '/config/app_dev.lua';
    ...
}

配置文件的内容直接使用lua语法

--[[
应用配置文件
--]]
mysql = {
    host = "10.1.1.2",
    port = "3306",
    user = "root",
    password = "lajin2015",
    database = "cms_admin",
    timeout = 30000,
    pool_size = 1000
}
redis = {
    host = "10.1.1.4",
    port = "6382",
    auth = "[email protected]",
    timeout = 30000,
    pool_size = 1000
}

include = { "/config/app_dev_a.lua", "/config/app_dev_b.lua" }

include 是一个特殊的用法,支持配置文件嵌套引入。

配置文件的内容在代码中,可以通过luastar_config.getConfig来获取:

local mysqlDataSource = luastar_config.getConfig("mysql")
local mysqlDataSourceHost = luastar_config.getConfig("mysql")["host"]

配置文件的内容也可以直接在bean.lua中使用,

mysql = {
    class = "luastar.db.mysql",
    arg = {
        { value = "${mysql}" }
    }
}

详情请参考bean的配置用法。

3.7 频次控制/路由/拦截器

频次控制/路由/拦截器在demo/config/route.lua文件中配置

-- 频次限制
limit = { class = "com.luastar.demo.ctrl.test.limit", method = "limit" }

-- 全匹配路由,优先级高 route = { { "", "/api/test/hello", "com.luastar.demo.ctrl.test.hello", "hello", { p1="v1", p2="v2" } }, { "POST", "/api/test/pic", "com.luastar.demo.ctrl.test.hello", "pic" }, { "", "/api/test/mysql", "com.luastar.demo.ctrl.test.mysql", "mysql" }, { "*", "/api/test/mysql/transaction", "com.luastar.demo.ctrl.test.mysql", "transaction" }, { "GET,POST", "/api/test/redis", "com.luastar.demo.ctrl.test.redis", "redis" } }

-- 模式匹配路由 route_pattern = { { "", "/aaa/.", "com.luastar.demo.ctrl.test.dispatcher", "aaa", { p1="v1", p2="v2" } }, -- aaa { "", "/bbb/.", "com.luastar.demo.ctrl.test.dispatcher", "bbb" }, -- bbb { "", "/ccc/.", "com.luastar.demo.ctrl.test.dispatcher", "ccc" }, -- ccc { "", "/.", "com.luastar.demo.ctrl.test.dispatcher", "other" } -- 默认 }

-- 拦截器配置,注:拦截器必须实现beforeHandle和afterHandle方法 interceptor = { { url = { { "", "/api/.", true } }, class = "com.luastar.demo.interceptor.common" } }

3.7.1 频次控制

频次限制需要配置实现的类和方法,方法需要返回布尔类型表示是否受限制,目前实现了ip限制,频次限制,单位时间内次数限制,基于 resty.limit 或 redis实现,resty.limit中的count实现需要openresty更高版本(支持dict:expire方法)。

参考: luastar/src/luastar/util/limit.lua demo/src/com/luastar/demo/ctrl/test/limit.lua

3.7.2 路由

路由分为全匹配路由和模式匹配路由,全匹配优先级高,不支持路径取值(不建议),模式使用lua自带的模式。

路由是一个二维数组,每一行表示一个接口地址,第一列表示请求方式(*表示不限制,多个请求方式用逗号分隔,v1.4版本新增),第二列表示请求地址,第三列表示对应的处理类,第四列表示处理类中的方法,第五列表示自定义参数(以第三个参数传到处理类方法中)

luastar默认给ctrl类请求处理方法传入了request/response对象(其他地方可通过ngx.ctx.request和ngx.ctx.response获取)和路由中第五列的自定义参数,用于处理输入和输出和路由扩展。

参考: demo/src/com/luastar/demo/ctrl/test/hello.lua

3.7.3 拦截器

拦截器每一行表示一个拦截器(优先级取决于数组顺序),url为数组,支持同时拦截多个url,每个url是一个数组(第一列表示拦截的请求方法,*代表所有,第二列可以是模式的,取决于第三列),class代表拦截器实现,excludes表示该拦截器不处理的请求数组。

注:1.2 版本前后结构不同

拦截器要实现两个方法beforeHandle和afterHandle,beforeHandle必须返回布尔类型的结果,只要有一个拦截器返回false,则ctrl不会执行,beforeHandle可以返回第二个参数(字符串类型),用于返回false后的输出结果(返回true时忽略)

参考:demo/src/com/luastar/demo/interceptor/common.lua

3.8 lua bean 管理

luastar实现了简化版的spring bean factory,默认将bean实例化后以单例模式(每个worker一份)存在缓存中,和require进来的对象相比,最大的区别是lua bean是用类实例化出来的对象,可以是单例的,也可以是多实例的,有自己的属性和方法。

3.8.1 定义bean

bean在配置文件demo/config/bean.lua文件中配置,注意保证id的唯一性

--[[
id = { -- bean id
    class = "", -- 类地址
    arg = { -- 构造参数注入
        {value/ref = ""} -- value赋值,ref引用其他bean
    },
    property = { -- set方法注入,实现set_${name}方法
        {name = "",value/ref = ""}
    },
    init_method = "", -- 初始化方法,默认使用init()
    single = 0 -- 是否单例,默认是1
}
--]]
-- mysql服务
mysql = {
    class = "luastar.db.mysql",
    arg = {
        { value = "${mysql}" }
    }
}
-- redis服务
redis = {
    class = "luastar.db.redis",
    arg = {
        { value = "${redis}" }
    }
}
-- 系统用户服务
userService = {
    class = "com.luastar.demo2.service.system.userService"
}
-- 功能服务
funcService = {
    class = "com.luastar.demo2.service.system.funcService"
}
-- 角色服务
roleService = {
    class = "com.luastar.demo2.service.system.roleService"
}
-- 角色关系服务
userRoleRelationService = {
    class = "com.luastar.demo2.service.system.userRoleRelationService"
}
-- 引入其他模块
_include_ = {
    "/config/bean_uc.lua"
}

bean配置文件也支持include引入其他配置的语法。 在类中定义的方法最好使用类的模式,存储私有变量,可以使用luastar框架中的class类定义。 参考: demo2/src/com/luastar/demo2/service/system/userService.lua

3.8.2 使用bean

在代码中先获取bean工厂,再获取bean

function _M.list(request, response)
    local param = {
        draw = request:get_arg("draw"),
        start = tonumber(request:get_arg("start")) or 0,
        limit = tonumber(request:get_arg("length")) or 10,
        keyword = request:get_arg("query_username")
    }
    -- 查询结果
    local beanFactory = luastar_context.getBeanFactory()
    local userService = beanFactory:getBean("userService")
    local num = userService:countUser(param);
    local data = {}
    if num > 0 then
        data = userService:getUserList(param);
    end
    -- 返回结果
    local result = {
        draw = param["draw"],
        recordsTotal = num,
        recordsFiltered = num,
        data = data
    }
    response:writeln(json_util.toJson(result, true))
end

3.9 mysql / redis 封装及使用

luastar中对mysql和redis的操作基于openresty官方提供的组件: LuaRestyMySQLLibrary LuaRestyRedisLibrary

luastar中对mysql和redis提供了以下功能: 1. 数据源配置 2. 获取连接 3. 关闭连接(使用连接池) 4. mysql事务 5. sql语句动态拼装 6. 未关闭连接监控

3.9.1 配置数据源

demo2/conf/app.lua中配置相关数据源,例如:

mysql = {
      host = "127.0.0.1",
      port = "3306",
      user = "root",
      password = "xxx",
      database = "xxx",
      timeout = 30000,
      pool_size = 1000
}
redis = {
      host = "127.0.0.1",
      port = "6379",
      auth = "xxx",
      timeout = 30000,
      pool_size = 1000
}

配置bean

demo2/conf/bean.lua中配置mysql/redis bean,多数据源可以配置多个,id不一样即可。

mysql = {
    class = "luastar.db.mysql",
    arg = {
        { value = "${mysql}" }
    }
}
redis = {
    class = "luastar.db.redis",
    arg = {
        { value = "${redis}" }
    }
}

3.9.3 使用

-- 获取封装类
local beanFactory = luastar_context.getBeanFactory()
local mysql_util = beanFactory:getBean("mysql")
local redis_util = beanFactory:getBean("redis")

-- 对于单次请求操作,可直接使用下列语句,不用获取和关闭连接 mysql_util.query("sql") redis_util.hgetall("key")

-- 对于多次请求操作,需要先获取到连接,依次执行,最后关闭连接 local mysql = mysql_util:getConnect() local res1, err1, errno1, sqlstate1 = mysql:query(sql1) local res2, err2, errno2, sqlstate2 = mysql:query(sql2) mysql_util:close(mysql)

local redis = redis_util:getConnect() local userinfo = table_util.array_to_hash(redis:hgetall("user:info:" .. uid)) redis_util:close(redis)

3.9.4 动态sql语句

local _M = {}

local sql_util = require("luastar.util.sql")

function _M.mysql(request, response) local name = request:get_arg("name") or "" local sql_table = { sql = [[ select * from SYS_USER @{where} order by ID desc limit #{start},#{limit} ]], where = { "LOGIN_NAME = #{loginName}", [[ and USER_NAME like concat('%',#{userName},'%') ]] } } local data = { userName = name, start = 0, limit = 10 } local sql = sql_util.getsql(sql_table, data) local beanFactory = luastar_context.getBeanFactory() local mysql_util = beanFactory:getBean("mysql") local mysql = mysql_util:getConnect() local res, err, errno, sqlstate = mysql:query(sql) mysql_util:close(mysql) response:writeln(cjson.encode({ sql = sql, res = res, err = err, errno = errno, sqlstate = sqlstate })) end

function _M.transaction(request, response) local beanFactory = luastar_context.getBeanFactory() local mysql_util = beanFactory:getBean("mysql") local sqlArray = { "update SYS_USER set USER_NAME='管理员1' where ID=1", "update SYS_USER set USER_NAME_A='管理员2' where ID=1" -- USER_NAME_A not exists } local result_table = mysql_util:queryTransaction(sqlArray) response:writeln(cjson.encode(result_table)) end

return _M

完整配置如下:

-- #{},如果值为字符串,则增加单引号防sql注入,如果为空,处理为null
-- ${},直接替换,如果为空,处理为null
-- @{},引用其他语句
sql_table = {
    sql = [[
        update SYS_USER @{set} @{where} @{limit}
    ]],
    set = {
        "USER_NAME = #{userName}",  -- userName为nil时忽略
        "UPDATED_TIME = #{updatedTime}"
    },
    where = {
        "LOGIN_NAME = #{loginName}",    -- loginName为nil时该语句忽略
        [[
            and USER_NAME like concat('%',#{userName},'%') -- userName为nil时该语句忽略
        ]]
    },
    limit = {
        start = "${start}", -- start和limit为nil时忽略
        limit = "${limit}"
    }
}

3.9.4 链接监控

luastar默认开启了mysql和redis的未关闭连接监控,如果有没有关闭的连接,会输出错误日志:

2016/12/20 16:34:23 [error] 40144#0: *45 [lua] monitor.lua:42: check(): check info +...luastar/db/mysql.lua:73, client: 127.0.0.1, server: localhost, request: "GET /api/test/mysql/transaction HTTP/1.1", host: "localhost:8001"

加号代表开启了连接的位置,减号代表关闭了连接的位置,如果有不匹配的+和-,则能定位到未关闭的位置,如果一次请求中开启和关闭的次数太多,日志可能输出不全(ngx.log的限制)。

4 web 开发

4.1 session 实现

luastar中session的管理使用的是第三方库: lua-resty-session

session已放入到全局变量中,可以在代码中直接使用,支持cookie、shm、memcache和redis持久化方式。

session保存

-- session保存
ngx.log(logger.i("保存session: ", cjson.encode(userInfo)))
session.save("user", userInfo)

session校验

function _M.beforeHandle()
    -- session校验
    if session.check() then
        local data = session.getData("user")
        ngx.log(logger.i("用户session验证通过", cjson.encode(data)))
        return true
    end
    ngx.log(logger.i("用户session验证不通过"))
    local xRequestedWith = ngx.ctx.request:get_header("x-requested-with")
    if _.isEmpty(xRequestedWith) then
        template.render("login.html", { message = "登录超时!" })
    else
        ngx.ctx.response:set_header("session-status", "timeout");
        ngx.ctx.response:writeln(json_util.timeout())
    end
    return false
end

session数据获取

-- 登录用户信息
local userInfo = session.getData("user")

session销毁

-- 销毁session
session.destroy()

其他用法可参考官方文档

4.2 页面布局和渲染

luastar中页面布局和渲染使用第三方库 lua-resty-template

配置web相关目录,相比api项目,需要额外配置template模板根路径和静态文件访问, 详见:luastar/conf/luastar.conf 中 demo2 的配置

template页面渲染语法在这里不多介绍了,请参考 lua-resty-template 和 demo2 中的实现。

sql语句在 demo2/src/resources/luastar-cms.sql 中,配置好数据源后可使用 admin / admin 登录系统,目前只实现了登录和用户管理的简单功能,1.3版本中前端框架改用 layui,整体简洁了不少。

5 联系方式

luastar 完全开源,不限制,欢迎使用和交流。

QQ交流群:545501138

Email:191026[email protected]

We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.