diff --git a/README.md b/README.md index af3aa50..80cd2c7 100644 --- a/README.md +++ b/README.md @@ -87,8 +87,38 @@ return _M ``` +> 访问结果 + +```shell +curl https://api.lua-china.com/index?id=1&foo=bar + +{ + "msg": "request args", + "status": 0, + "data": { + "foo": "bar", + "id": "1" + } +} +``` + ## 压力测试 +### 不使用数据库只输出 json + +#### 阿里云单核4G内存 + +```shell +ab -c 100 -n 10000 api.lua-china.com/index + +--- +Requests per second: 2220.06 [#/sec] (mean) +Time per request: 45.044 [ms] (mean) +--- +``` + +> 内存基本没有变化,单核 CPU 打满 + ### 单次 mysql 数据库查询 #### mac 4核 i7 16G 内存 固态硬盘 @@ -111,8 +141,7 @@ Time per request: 31.992 [ms] (mean) ### 本地化 -通过给 `ngx.ctx.locale` 赋值来更换语言环境,如: -`ngx.ctx.locale = zh` +使用 local 的 middleware 加在路由前,该中间件通过给 `ngx.ctx.locale` 赋值来更换语言环境:`ngx.ctx.locale = zh` ### 路由 @@ -213,7 +242,7 @@ local data = cjson.decode(res.body) 框架使用的 `lib/response.lua` 中的 `json` 方法通过定义数字来代表不同的`response`类型,该方法支持三四个参数 1. 第一个参数是状态码,16进制状态码对应 `config/status.lua` -2. 第二个参数是错误码文案,文案根据第一个参数对应 `config/status.lua` 中的文案 +2. 第二个参数是错误码文案,默认值是根据第一个参数对应 `config/status.lua` 中的文案 3. 第三个参数是需要向前端返回的数据,可省略 4. 第四个参数是返回的 `http 状态码`,可省略,默认是200 @@ -524,9 +553,7 @@ table_remove(tab, {'item1', 'item2'}) > lua 中的`hash table`按`key`排序与其他语言不同,我们需要自己实现一个迭代器来遍历排好序的`table` ```lua -for k,v in pairsByKeys(hashTable) do - ... -end +sort_by_key(hashTable) ``` ## 代码规范 diff --git a/bootstrap.lua b/bootstrap.lua index 03caf7e..d03818d 100644 --- a/bootstrap.lua +++ b/bootstrap.lua @@ -1,14 +1,5 @@ -- Github Document: https://github.com/horan-geeker/nana -- Author: hejunwei --- Version: v0.4.0 +-- Version: v0.5.0 -local Core = {} - -function Core:bootstrap() - -- get helper function - require('lib.helpers'):init(_G) - -- run application - require("lib.application"):init():run() -end - -Core:bootstrap() \ No newline at end of file +require("lib.application"):init(_G):run() \ No newline at end of file diff --git a/config/status.lua b/config/status.lua index 02abea3..f0ce82a 100644 --- a/config/status.lua +++ b/config/status.lua @@ -25,6 +25,8 @@ return { [0x010007] = '重置密码失败,新密码不能和旧密码相同', [0x010008] = '获取用户信息失败,系统错误', [0x010009] = '重置密码失败,用户不存在', + [0x01000A] = '获取用户信息失败,用户未登录', + [0x01000B] = '获取用户信息失败,用户不存在', -- notify module [0x020001] = '发送短信验证码失败,请在60秒之后重试', diff --git a/controllers/notify/sms_notify_controller.lua b/controllers/notify/sms_notify_controller.lua index 20f02f3..31e2d5c 100644 --- a/controllers/notify/sms_notify_controller.lua +++ b/controllers/notify/sms_notify_controller.lua @@ -20,7 +20,7 @@ function _M:guest_send_sms() if not ok then return response:json(1, err) end - local res = sms_service:sendSMS(args['phone']) + local res = sms_service:send_sms(args['phone']) if res ~= true then return response:json(res) end @@ -29,7 +29,7 @@ end function _M:user_send_sms() local user = auth:user() - local res = sms_service:sendSMS(user.phone) + local res = sms_service:send_sms(user.phone) if res ~= true then return response:json(res) end diff --git a/controllers/user_controller.lua b/controllers/user_controller.lua index 2babf03..bc61140 100644 --- a/controllers/user_controller.lua +++ b/controllers/user_controller.lua @@ -7,6 +7,9 @@ local _M = {} function _M:show(id) local user = User:find(id) + if not user then + return response:json(0x01000B) + end return response:json(0, '', user) end diff --git a/lib/application.lua b/lib/application.lua index 1e364a7..b57eeab 100644 --- a/lib/application.lua +++ b/lib/application.lua @@ -1,12 +1,28 @@ +local router = require('lib.router') +local request = require('lib.request') +local database = require('lib.database') + local _M = {} -function _M:init() +function _M:init(G) + -- init helper function + require('lib.helpers'):init(G) + -- init route + router:init() return self end function _M:run() - -- dispatche route to controller - require('lib.router'):init() + -- match route, dispatch request to action + local http_response, http_status = router:run() + -- do some thing after run action + self:terminate(request:all(), response_content) + -- dispatch route to controller + require('lib.response'):send(http_response, http_status) +end + +function _M:terminate(request, resposne) + database:close() end return _M \ No newline at end of file diff --git a/lib/database.lua b/lib/database.lua index 2cb6c8a..e085afb 100644 --- a/lib/database.lua +++ b/lib/database.lua @@ -42,13 +42,6 @@ function _M:get_connection() return nil, err end -- ngx.log(ngx.WARN, self.db_type, ' mysql connect') - -- set time zone - local query = 'SET time_zone = "'..self.time_zone..'"' - local res, err, errcode, sqlstate = db:query(query) - if not res then - ngx.log(ngx.ERR, res, err, errcode, sqlstate) - return nil, err - end ngx.ctx[self.db_type] = db return db, nil end @@ -79,7 +72,7 @@ end @return bool, data, err --]] function _M.mysql_query(self, sql) - -- ngx.log(ngx.ERR, self.db_type, sql) + -- ngx.log(ngx.WARN, self.db_type, sql) local db, err = self:get_connection() if err ~= nil then return nil, err @@ -106,7 +99,6 @@ function _M.new(self, opts) max_packet_size = 1024 * 1024, db_pool_timeout = opts.pool_timeout or 1000, db_pool_size = opts.pool_size or 1000, - time_zone = opts.time_zone or '+8:00', db_type = opts.db_type, }, mt) end diff --git a/lib/helpers.lua b/lib/helpers.lua index 7a4b604..3c687a3 100644 --- a/lib/helpers.lua +++ b/lib/helpers.lua @@ -1,6 +1,3 @@ -local cookie_obj = require("lib.cookie") -local cjson = require("cjson") - local _M = {} function _M:init(G) @@ -36,7 +33,7 @@ function _M:init(G) -- remove item in table function G.table_remove(tab, rm) local result = tab - for k, v in pairs(rm) do + for k, v in pairs(rm) do for a_k, a_v in pairs(result) do -- array if type(a_k) == 'number' then @@ -51,8 +48,8 @@ function _M:init(G) if v == a_k then result[a_k] = nil end - end - end + end + end end return result end @@ -78,21 +75,20 @@ function _M:init(G) return string.sub(implode_str, 1, #implode_str - 1) end -- sort a hashTable by key - -- use example: for k,v in pairsByKeys(hashTable) - function G:pairsByKeys(f) + function G.sort_by_key(tab) local a = {} - for n in pairs(self) do + for n in pairs(tab) do table.insert(a, n) end - table.sort(a, f) + table.sort(a) local i = 0 -- iterator variable local iter = function() -- iterator function i = i + 1 - if a[i] == nil then - return nil + if a[i] then + return a[i], tab[a[i]] else - return a[i], self[a[i]] + return nil end end return iter @@ -100,7 +96,7 @@ function _M:init(G) function G.set_cookie(key, value, expires) local config = require("config.app") - local cookie, err = cookie_obj:new() + local cookie, err = require("lib.cookie"):new() if not cookie then ngx.log(ngx.ERR, err) return false, err @@ -123,9 +119,8 @@ function _M:init(G) return true end - function G:get_cookie() - local key = self - local cookie, err = cookie_obj:new() + function G.get_cookie(key) + local cookie, err = require("lib.cookie"):new() if not cookie then ngx.log(ngx.ERR, err) return false @@ -133,7 +128,7 @@ function _M:init(G) return cookie:get(key) end - function G:get_local_time() + function G.get_local_time() local config = require("config.app") local time_zone = ngx.re.match(config.time_zone, "[0-9]+") if time_zone == nil then @@ -146,24 +141,15 @@ function _M:init(G) return time_zone[0] * 3600 + ngx.time() end - function G.purge_uri(uri) - local uri = string.gsub(uri, "?.*", "") - local uri_without_slash = remove_slash(uri) - return uri_without_slash - end - - function G.remove_slash(target) - local len = string.len(target) - if string.find(target,'/', len) then - return string.sub(target, 1, len-1) - end - return target + function G.trim(str, symbol) + symbol = symbol or '%s' -- %s default match space \t \n etc.. + return (string.gsub(string.gsub(str, '^' .. symbol .. '*', ""), symbol .. '*$', '')) end function G.hash(password) return ngx.md5(password) end - + -- data not in order function G.log(...) local args = {} @@ -172,7 +158,7 @@ function _M:init(G) else args = ... end - ngx.log(ngx.WARN, cjson.encode(args)) + ngx.log(ngx.WARN, require("cjson").encode(args)) end end diff --git a/lib/model.lua b/lib/model.lua index ba582aa..0591339 100644 --- a/lib/model.lua +++ b/lib/model.lua @@ -20,7 +20,6 @@ local database_write = Database:new({ timeout = database_config.mysql.timeout, db_pool_timeout = database_config.mysql.pool_timeout, db_pool_size = database_config.mysql.pool_size, - time_zone = config.time_zone, db_type = WRITE }) @@ -34,7 +33,6 @@ local database_read = Database:new({ timeout = database_config.mysql.timeout, db_pool_timeout = database_config.mysql.pool_timeout, db_pool_size = database_config.mysql.pool_size, - time_zone = config.time_zone, db_type = READ }) diff --git a/lib/request.lua b/lib/request.lua index 13b61a4..4f1b4df 100644 --- a/lib/request.lua +++ b/lib/request.lua @@ -42,4 +42,12 @@ function _M:headers() return ngx.req.get_headers() end +function _M:get_uri() + return string.gsub(ngx.var.request_uri, '?.*$', '') +end + +function _M:get_method() + return ngx.var.request_method +end + return _M \ No newline at end of file diff --git a/lib/response.lua b/lib/response.lua index f1382ce..725b968 100644 --- a/lib/response.lua +++ b/lib/response.lua @@ -28,4 +28,11 @@ function _M:error(error_message) ngx.exit(500) end +function _M:send(content, status) + if content ~= nil and content ~= '' then + ngx.say(content) + end + ngx.exit(status) +end + return _M diff --git a/lib/router.lua b/lib/router.lua index f274c85..3f8c1ec 100644 --- a/lib/router.lua +++ b/lib/router.lua @@ -1,15 +1,15 @@ -local cjson = require('cjson') -local response = require('lib.response') -local database = require('lib.database') -local controller_prefix = 'controllers.' -local middleware_prefix = 'middleware.' - -local _M = {} - -local function route_match(route_url, http_url) - local new_router_url, n, err = ngx.re.gsub(route_url, '\\{[\\w]+\\}', '(\\d+)') - new_router_url = '^' .. new_router_url .. '$' - local captures, err = ngx.re.match(http_url, new_router_url, 'jo') +local request = require('lib.request') + +local controller_prefix = 'controllers' +local middleware_prefix = 'middleware' + +local _M = { + routes = {}, + middlewares = {} +} + +function _M:route_match(route_uri, http_uri) + local captures, err = ngx.re.match(http_uri, route_uri, 'jo') if captures then captures[0] = nil; return true, captures @@ -17,100 +17,114 @@ local function route_match(route_url, http_url) return false end -local function find_action(controller, action) - if type(require(controller_prefix..controller)) ~= 'table' then +function _M:find_action(controller, action) + local controller = require(controller_prefix .. '.' .. controller) + if type(controller) ~= 'table' then return nil, 'system error, controller not a table' end - local action = require(controller_prefix..controller)[action] + local action = controller[action] if action == nil then return nil, 'system error, this action function not exist' end return action, nil end -function _M:init() - ngx.ctx.middleware_group = {} - require('routes'):match(self) - return ngx.exit(ngx.HTTP_NOT_FOUND) +function _M:run_middlewares(middlewares) + for _, middleware in ipairs(middlewares) do + local result, response_content, resposne_status = require(middleware_prefix .. '.' ..middleware):handle() + if result == false then + return result, response_content, resposne_status + end + end end -function _M:send_response(content, status) - database:close() - if content ~= nil then - ngx.say(content) - end - ngx.exit(status) -end - -function _M:call_action(method, uri, controller, action) - local matched, params = route_match(purge_uri(uri), purge_uri(ngx.var.request_uri)) - if matched then - if method == ngx.var.request_method then - if ngx.ctx.middleware_group then - for _,middleware in ipairs(table_reverse(ngx.ctx.middleware_group)) do - local result, response_content, resposne_status = require(middleware_prefix..middleware):handle() - if result == false then - return self:send_response(response_content, resposne_status) - end - end +function _M:dispatch(http_uri, http_method) + for uri, route in pairs(self.routes) do + local is_matched, route_params = self:route_match(uri, http_uri) + if is_matched then + if not route[http_method] then + return nil, 405 end - if controller then - called_action, err = find_action(controller, action) - if err ~= nil then - return self:send_response(response:error(err)) - end - local response_content, resposne_status = called_action(nil, table.unpack(params)) - return self:send_response(response_content, resposne_status) - else - return ngx.log(ngx.WARN, 'upsteam api') + -- run middlewares + local result, response_content, resposne_status = self:run_middlewares(route[http_method].middlewares) + if result == false then + return response_content, resposne_status end - else - if ngx.var.request_method == 'OPTIONS' then - called_action, err = find_action(controller, action) - if err ~= nil then - return self:send_response(response:error(err)) - end - return self:send_response(nil, ngx.OK) + -- run action + local action = self:find_action(route[http_method]['controller'], route[http_method]['action']) + if not action then + return 'not found action in controller', 500 end + return action(nil, table.unpack(route_params)), ngx.OK end - -- router will throw 404 in router.lua end + return nil, 404 +end + +function _M:run() + return self:dispatch(self:trim_uri(request:get_uri()), request:get_method()) +end + +function _M:init() + self.middlewares = {} + require('routes'):match(self) +end + +function _M:trim_uri(uri) + return '/' .. trim(trim(uri, ' '), '/') .. '/' +end + +function _M:transform_uri(uri) + local trimed_uri = self:trim_uri(uri) + local new_router_url, n, err = ngx.re.gsub(trimed_uri, '\\{[\\w]+\\}', '(\\d+)') + return '^' .. new_router_url .. '$' +end + +function _M:add_route(method, uri, controller, action) + local pattern_uri = self:transform_uri(uri) + if not self.routes[pattern_uri] then + self.routes[pattern_uri] = {} + end + self.routes[pattern_uri][method] = { + controller = controller, + action = action, + middlewares = {table.unpack(self.middlewares)} -- deep copy self.middlewares + } end function _M:get(uri, controller, action) - _M:call_action('GET', uri, controller, action) + self:add_route('GET', uri, controller, action) end function _M:post(uri, controller, action) - _M:call_action('POST', uri, controller, action) + self:add_route('POST', uri, controller, action) end function _M:put(uri, controller, action) - _M:call_action('PUT', uri, controller, action) + self:add_route('PUT', uri, controller, action) end function _M:patch(uri, controller, action) - _M:call_action('PATCH', uri, controller, action) + self:add_route('PATCH', uri, controller, action) end function _M:delete(uri, controller, action) - _M:call_action('DELETE', uri, controller, action) + self:add_route('DELETE', uri, controller, action) end function _M:head(uri, controller, action) - _M:call_action('HEAD', uri, controller, action) + self:add_route('HEAD', uri, controller, action) end -function _M:group(middleware, func) - -- index always be 1 and data will push back one by one - for index,middleware_item in ipairs(middleware) do - table.insert(ngx.ctx.middleware_group, index, middleware_item) +function _M:group(middlewares, func) + -- append one by one tail + for _,middleware in ipairs(middlewares) do + table.insert(self.middlewares, middleware) end func() - -- if not match any route, remove part of middleware - for index,middleware_item in ipairs(middleware) do - -- remove middleware - table.remove(ngx.ctx.middleware_group, index) + -- remove tail first + for index,_ in ipairs(middlewares) do + table.remove(self.middlewares, #self.middlewares) end end diff --git a/services/sms_service.lua b/services/sms_service.lua index 066270d..1e6be65 100644 --- a/services/sms_service.lua +++ b/services/sms_service.lua @@ -8,11 +8,11 @@ local _M = { SMS_KEY = 'SMS:PHONE:%s' } -local function generateSendCloudSignature(phone, code) +local function generate_sendcloud_signature(phone, code) local conf = config.sendcloud local signStr = '' - local params = {smsUser=conf['smsUser'],templateId=conf['templateId'],phone=phone,vars={code=code}} - for k,v in pairsByKeys(params) do + local params = {smsUser=conf.smsUser,templateId=conf.templateId,phone=phone,vars={code=code}} + for k,v in sort_by_key(params) do local val = '' if type(v) == 'table' then val = cjson.encode(v) @@ -25,7 +25,7 @@ local function generateSendCloudSignature(phone, code) return ngx.md5(signature), signStr end -local function sendMessageToSendCloud(phone, code, signature, signStr) +local function send_message_to_sendcloud(phone, code, signature, signStr) local url = config['sendcloud']['url'] .. '?' .. signStr .. 'signature='..signature local httpClient = http.new() local res, err = httpClient:request_uri(url, {ssl_verify=false}) @@ -45,7 +45,7 @@ local function sendMessageToSendCloud(phone, code, signature, signStr) end end -function _M:sendSMS(phone) +function _M:send_sms(phone) local key = string.format(self.SMS_KEY, phone) local data, err = redis:get(key) if err then @@ -70,13 +70,13 @@ function _M:sendSMS(phone) return 0x000007 end local ok, err = ngx.timer.at(0, function (premature, phone, smscode) - local signature, signStr = generateSendCloudSignature(tonumber(phone), smscode) - local ok, err = sendMessageToSendCloud(tonumber(phone), smscode, signature, signStr) - if not ok then - ngx.log(ngx.ERR, err) - end - end, - phone, smscode) + local signature, signStr = generate_sendcloud_signature(tonumber(phone), smscode) + local ok, err = send_message_to_sendcloud(tonumber(phone), smscode, signature, signStr) + if not ok then + ngx.log(ngx.ERR, err) + end + end, + phone, smscode) if not ok then return 0x00000B end