/luastar

一个用lua实现的基于openresty的接口(api)开发框架

Primary LanguageLua

#luastar ##1. 简介 luastar是一个基于openresty的高性能高并发开发框架,主要用于移动端app的http接口开发,实现了request/response、缓存、配置文件、路由/拦截器、bean管理、mysql和redis以及httpclient等常用工具类的封装,便于快速开发。 ##2. 安装 ###2.1 openresty 安装 请参考官网介绍,建议安装目录:/usr/local/openresty ###2.2 luastar 安装 下载项目到到硬盘上,如:/data/apps/luastar下。 可修改配置文件(luastar/conf/luastar*.conf)中的相关路径为openresty安装路径和项目存放路径,可配置多个不同环境的配置文件。 例如: 开发环境(luastar_dev.conf)中增加了调试工具路径并关闭了代码缓存。 开发调试可使用ZeroBrane Studio,详细请参考后继章节。 关闭代码缓存可在修改代码后不必每次重启nginx。 ###2.3 nginx 配置 修改 openresty/nginx/conf/nginx.conf,最后一行增加: include /data/apps/luastar/luastar/conf/luastar_dev.conf; ###2.4 hello world 启动openresty: openresty/nginx/sbin/nginx -c openresty/nginx/conf/nginx.conf

访问 http://localhost:8001/api/test/hello, 试试带参数访问 http://localhost:8001/api/test/hello?name=haha

##3. 开始 ###3.1 项目结构

  • luastar
  • |----luastar
  • |--------conf(nginx配置文件)
  • |--------libs(第三方库)
  • |--------src(luastar源码)
  • |----demo1(项目1)
  • |--------config(项目配置)
  • |------------app.lua(项目配置文件)
  • |------------bean.lua(bean配置文件)
  • |------------route.lua(路由/拦截器配置文件)
  • |--------src(项目源码)
  • |------------com
  • |----------------luastar
  • |--------------------demo
  • |------------------------ctrl(控制类-业务逻辑)
  • |------------------------interceptor(拦截器)
  • |------------------------service(服务类-公共服务)
  • |------------------------util(常用类)
  • |----demo2(项目2)
  • |--------config(项目配置)
  • |--------src(项目源码)

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

变量名 用途
Class 类定义
cjson json处理类
_ Moses常用工具类
luastar_cache 缓存
luastar_config 配置
luastar_context 上下文
logger 日志辅助

第三方工具类:

###3.3 缓存 在项目中,如果有需要缓存的数据,可使用luastar_cache来存放和读取

luastar_cache.get("app_config")
luastar_cache.set("app_config", app_config)

注:luastar的缓存根据openresty的机制,每个nginx的worker会存放一份,如果需要在worker**用,请使用openresty提供的字典(支持的数据结构有限)

###3.4 上下文

--获取路由
local route = luastar_context.getRoute()
--获取bean
local beanFactory = luastar_context.getBeanFactory()
local redis_util = beanFactory:getBean("redis")

###3.5 日志 luastar日志直接使用openresty中提供的ngx.log实现,之前有使用第三方log包写文件,但效果不太理想,容易丢失日志。 luastar提供了一个辅助类,主要用于日志跟踪。

ngx.log(logger.info(p1,p2,p3,...))
-- 也可以使用简写
ngx.log(logger.i(p1,p2,p3,...))

设计在每个请求头中增加一个参数random,如果客户端传入了此参数,则直接使用,如果没传,则随机生成,在使用上述方式输出的日志中都会带有该标识,例如:--[ECekJjHCK5]--。

2015/12/14 13:31:06 [info] 3102#0: *26 [lua] common.lua:20: --[3SLqq8d1Zn]--request header is {"datakey":"","random":"3SLqq8d1Zn","ostype":"","appkey":"","appversion":""}, client: 127.0.0.1, server: localhost, request: "GET /api/test/hello HTTP/1.1", host: "localhost:8001"

###3.6 配置文件 项目配置可根据不同环境配置多个, 例如在测试环境的luastar/conf/luastar_test.conf中设置: set $APP_CONFIG '/config/app_test.lua';

server {
  listen 8001;
  server_name localhost;
  set $LUASTAR_PATH '/data/apps/luastar/luastar';
  set $APP_NAME 'demo';
  set $APP_PATH '/data/apps/luastar/demo';
  set $APP_CONFIG '/config/app_test.lua';
  access_log /data/logs/demo/access.log  main;
  error_log  /data/logs/demo/error.log   info;
  location / {
    default_type text/html;
    content_by_lua_file '${LUASTAR_PATH}/src/luastar_content.lua';
  }
}

配置文件直接使用lua语法,例如:

--[[
应用配置文件
--]]
mysql = {
    host = "localhost",
    port = "3306",
    user = "admin",
    password = "xxx",
    database = "xxx",
    timeout = 30000,
    pool_size = 1000
}
redis = {
    host = "localhost",
    port = "6379",
    auth = "xxx",
    timeout = 30000,
    pool_size = 1000
}
weixin = {
  access_token_url = "https://api.weixin.qq.com/sns/oauth2/access_token",
  check_token_url = "https://api.weixin.qq.com/sns/auth",
  refresh_token_url = "https://api.weixin.qq.com/sns/oauth2/refresh_token",
  userinfo_url = "https://api.weixin.qq.com/sns/userinfo"
}

在代码中可通过luastar_config.getConfig来获取:

local access_token_url = luastar_config.getConfig("weixin")["access_token_url"]

也可以在bean.conf中通过${weixin.access_token_url}获取

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

配置文件在nginx启动时读取,并存放在缓存中。 ###3.7 路由和拦截器 路由和拦截器在demo/conf/route.lua文件中配置,例如:

route = {
    { "/api/test/hello", "com.luastar.demo.ctrl.test.hello", "hello" },
    { "/api/test/mysql", "com.luastar.demo.ctrl.test.mysql", "mysql" },
    { "/api/test/mysql/transaction", "com.luastar.demo.ctrl.test.mysql", "transaction" },
    { "/api/test/redis", "com.luastar.demo.ctrl.test.redis", "redis" },
    { "/api/test/baidu", "com.luastar.demo.ctrl.test.httpclient", "baidu" },
    { "/api/test/form", "com.luastar.demo.ctrl.test.form", "form" }
}

interceptor = {
    {
        url = "/api",
        class = "com.luastar.demo.interceptor.common"
    }
}

路由是一个二维数组,每一行表示一个接口地址,第一列表示请求地址(目前只支持全匹配),第二列表示对应的处理类,第三列表示处理类中的方法。 例如:当请求http://localhost:8001/api/test/hello时,由com.luastar.demo.ctrl.test.hello类的hello方法处理。 拦截器与路由稍有不同,每一行指定了属性,url代表拦截的请求,支持lua的模式匹配,class代表拦截器实现,excludes表示排除不处理的请求。

interceptor = {
  {url="url1", class="file"},
  {url="url2", class="file", excludes={"url1","url2"}}
}

拦截器必须实现beforeHandle和afterHandle方法 beforeHandle方法返回一个布尔类型的值,返回true继续执行后续处理,返回false中止退出。 ###3.8 bean配置 简化版的spring bean管理,

id = {	--bean id
  class = "", --类地址
  arg = { --构造参数注入
    {value/ref = ""} --value直接赋值,ref引用其他bean
  },
  property = { --set方法注入,必须实现set_${name}方法
    {name = "",value/ref = ""}
  },
  init_method = "",--初始化方法,默认使用init()
  single = 0  -- 是否单例,默认是
}

例如:

mysql = {
    class = "luastar.db.mysql",
    arg = {
        { value = "${mysql}" }
    }
}
redis = {
    class = "luastar.db.redis",
    arg = {
        { value = "${redis}" }
    }
}
paramService = {
    class = "com.lajin.service.common.paramService"
}
testService = {
    class = "com.lajin.service.test.testService",
    arg = { { ref = "redis" } }
}

注:在类中定义的方法最好使用类的模式,可以使用luastar框架中的class类定义:

local testService = Class("com.luastar.demo.service.test.testService")
local table_util = require("luastar.util.table")

function testService:init(redis_util)
    self.redis_util = redis_util
end

--[[
-- 根据uid获取用户信息
--]]
function testService:getUserInfo(uid)
    if _.isEmpty(uid) then
        return nil
    end
    local redis = self.redis_util:getConnect()
    local userinfo = table_util.array_to_hash(redis:hgetall("user:info:" .. uid))
    self.redis_util:close(redis)
    if _.isEmpty(userinfo) then
        ngx.log(logger.e("userinfo is empty, uid=", uid))
        return nil
    end
    ngx.log(logger.i(cjson.encode(userinfo)))
    return userinfo
end

return testService

在代码中调用:

local beanFactory = luastar_context.getBeanFactory()
local mysql_util = beanFactory:getBean("mysql")

###3.9 ctrl类 默认给ctrl类的请求处理方法传入了request和response对象,也可通过ngx.ctx.request和ngx.ctx.response获取

function hello(request, response)
    local name = request:get_arg("name") or "world, try to give a param with name."
    response:writeln("hello, " .. name)
end

可以通过request:get_arg("name", "default")获取参数,支持get、post参数,支持文件上传。