mdserver-web/plugins/webstats/lua/webstats_common.lua

1076 lines
31 KiB
Lua
Raw Normal View History

2022-10-16 12:44:04 -04:00
local setmetatable = setmetatable
local _M = { _VERSION = '1.0' }
local mt = { __index = _M }
local json = require "cjson"
local sqlite3 = require "lsqlite3"
local config = require "webstats_config"
local sites = require "webstats_sites"
local debug_mode = true
local total_key = "log_kv_total"
2022-10-17 09:12:42 -04:00
local unset_server_name = "unset"
local max_log_id = 99999999999999
2022-10-16 12:44:04 -04:00
local cache = ngx.shared.mw_total
2022-10-17 09:12:42 -04:00
2022-10-16 12:44:04 -04:00
local today = ngx.re.gsub(ngx.today(),'-','')
2022-10-17 09:12:42 -04:00
local request_header = ngx.req.get_headers()
2022-10-20 02:03:17 -04:00
local method = ngx.req.get_method()
2022-10-17 09:12:42 -04:00
2022-10-16 12:44:04 -04:00
local day = os.date("%d")
local number_day = tonumber(day)
local day_column = "day"..number_day
local flow_column = "flow"..number_day
local spider_column = "spider_flow"..number_day
2022-10-17 09:12:42 -04:00
local auto_config = nil
2022-10-16 14:04:01 -04:00
local log_dir = "{$SERVER_APP}/logs"
2022-10-16 12:44:04 -04:00
function _M.new(self)
local self = {
total_key = total_key,
params = nil,
site_config = nil,
config = nil,
}
return setmetatable(self, mt)
end
function _M.getInstance(self)
2023-02-11 12:02:19 -05:00
if self.instance == nil then
self.instance = self:new()
2023-02-11 12:08:03 -05:00
self:cron()
2022-10-16 12:44:04 -04:00
end
assert(self.instance ~= nil)
return self.instance
end
function _M.initDB(self, input_sn)
local path = log_dir .. '/' .. input_sn .. "/logs.db"
2026-07-02 00:52:05 -04:00
local db, err = sqlite3.open(path)
2022-10-17 00:17:37 -04:00
if err then
return nil
end
2022-10-16 12:44:04 -04:00
2022-10-16 23:36:29 -04:00
db:exec([[PRAGMA synchronous = 0]])
2022-10-17 13:20:08 -04:00
db:exec([[PRAGMA cache_size = 8000]])
db:exec([[PRAGMA page_size = 32768]])
2022-10-16 23:36:29 -04:00
db:exec([[PRAGMA journal_mode = wal]])
2023-11-20 10:40:07 -05:00
db:exec([[PRAGMA journal_size_limit = 21474836480]])
2022-10-16 12:44:04 -04:00
return db
end
function _M.getTotalKey(self)
return self.total_key
end
function _M.to_json(self, msg)
return json.encode(msg)
end
2022-10-16 12:44:04 -04:00
function _M.setConfData( self, config, site_config )
self.config = config
self.site_config = site_config
end
function _M.setParams( self, params )
self.params = params
end
2022-10-17 09:12:42 -04:00
function _M.setInputSn(self, input_sn)
local global_config = config["global"]
if config[input_sn] == nil then
auto_config = global_config
else
2023-01-14 00:46:59 -05:00
auto_config = config[input_sn]
2022-10-17 09:12:42 -04:00
for k, v in pairs(global_config) do
if auto_config[k] == nil then
auto_config[k] = v
end
end
end
return auto_config
end
function _M.get_domain(self)
local domain = ngx.req.get_headers()['host']
2022-11-30 23:43:31 -05:00
-- domain = ngx.re.gsub(domain, "_", ".")
if domain == nil then
2022-10-17 09:12:42 -04:00
domain = "unknown"
end
return domain
end
2022-10-16 12:44:04 -04:00
function _M.split(self, str, reps)
local arr = {}
2023-11-17 13:36:18 -05:00
-- 修复反向代理代过来的数据
2023-11-17 13:40:03 -05:00
if "table" == type(str) then
2023-11-17 13:36:18 -05:00
return str
end
2022-10-16 12:44:04 -04:00
string.gsub(str,'[^'..reps..']+',function(w) table.insert(arr,w) end)
return arr
end
2022-10-17 09:12:42 -04:00
function _M.arrlen(self, arr)
if not arr then return 0 end
local count = 0
for _,v in ipairs(arr) do
count = count + 1
end
return count
end
function _M.is_ipaddr(self, client_ip)
local cipn = self:split(client_ip,'.')
if self:arrlen(cipn) < 4 then return false end
for _,v in ipairs({1,2,3,4})
do
local ipv = tonumber(cipn[v])
if ipv == nil then return false end
if ipv > 255 or ipv < 0 then return false end
end
return true
end
function _M.get_sn(self, input_sn)
local dst_name = cache:get(input_sn)
if dst_name then return dst_name end
-- self:D(json.encode(sites))
for _,v in ipairs(sites)
do
if input_sn == v["name"] then
cache:set(input_sn, v['name'], 86400)
return v["name"]
end
2022-10-24 07:11:48 -04:00
-- self:D("get_sn:"..json.encode(v))
2022-10-17 09:12:42 -04:00
for _,dst_domain in ipairs(v['domains'])
do
if input_sn == dst_domain then
cache:set(input_sn, v['name'], 86400)
return v['name']
elseif string.find(dst_domain, "*") then
local new_domain = string.gsub(dst_domain, '*', '.*')
if string.find(input_sn, new_domain) then
dst_domain = v['name']
cache:set(input_sn, dst_domain, 86400)
end
end
end
end
cache:set(input_sn, unset_server_name, 86400)
return unset_server_name
end
function _M.get_store_key(self)
return os.date("%Y%m%d%H", ngx.time())
end
2022-10-18 05:41:29 -04:00
function _M.get_store_key_with_time(self, htime)
return os.date("%Y%m%d%H", htime)
end
2022-10-17 09:12:42 -04:00
function _M.get_length(self)
local clen = ngx.var.body_bytes_sent
if clen == nil then clen = 0 end
return tonumber(clen)
end
function _M.get_last_id(self, input_sn)
local last_insert_id_key = input_sn .. "_last_id"
local new_id, err = cache:incr(last_insert_id_key, 1, 0)
if new_id >= max_log_id then
cache:set(last_insert_id_key, 1)
new_id = cache:get(last_insert_id_key)
end
return new_id
end
function _M.get_http_origin(self)
local data = ""
2023-01-23 07:21:00 -05:00
local headers = ngx.req.get_headers()
2023-01-23 07:30:01 -05:00
if not headers then return data end
2023-03-09 01:56:43 -05:00
2026-07-02 00:52:05 -04:00
local req_method = ngx.req.get_method()
if req_method ~= 'GET' then
2023-01-06 14:00:55 -05:00
data = ngx.var.request_body
if not data then
data = ngx.req.get_body_data()
end
2022-10-17 09:12:42 -04:00
if "string" == type(data) then
headers["payload"] = data
2026-07-02 00:52:05 -04:00
elseif "table" == type(data) then
2023-01-06 14:00:55 -05:00
headers["payload"] = table.concat(data, "&")
2022-10-17 09:12:42 -04:00
end
end
return json.encode(headers)
end
2023-02-11 09:16:08 -05:00
function _M.cronPre(self)
2023-11-17 15:32:35 -05:00
self:lock_working('cron_init_stat')
2023-02-11 09:16:08 -05:00
local time_key = self:get_store_key()
local time_key_next = self:get_store_key_with_time(ngx.time()+3600)
2023-03-05 05:34:35 -05:00
2023-02-11 09:16:08 -05:00
for site_k, site_v in ipairs(sites) do
local input_sn = site_v["name"]
local db = self:initDB(input_sn)
local wc_stat = {
'request_stat',
'client_stat',
'spider_stat'
}
2023-03-05 05:34:35 -05:00
local v1 = true
local v2 = true
2023-02-11 09:16:08 -05:00
for _,ws_v in pairs(wc_stat) do
2023-03-05 05:34:35 -05:00
v1 = self:_update_stat_pre(db, ws_v, time_key)
v2 = self:_update_stat_pre(db, ws_v, time_key_next)
2023-02-11 09:16:08 -05:00
end
if db and db:isopen() then
db:execute([[COMMIT]])
db:close()
end
2023-03-05 05:34:35 -05:00
if not v1 or not v2 then
return false
end
2023-02-11 09:16:08 -05:00
end
2023-03-05 05:34:35 -05:00
2023-11-17 15:32:35 -05:00
self:unlock_working('cron_init_stat')
2023-03-05 05:34:35 -05:00
return true
2023-02-11 09:16:08 -05:00
end
2022-10-16 12:44:04 -04:00
function _M.cron(self)
2023-02-11 11:22:39 -05:00
2022-10-16 12:44:04 -04:00
local timer_every_get_data = function (premature)
2023-02-12 14:05:26 -05:00
2026-07-02 00:52:05 -04:00
local llen = ngx.shared.mw_total:llen(total_key)
2022-10-18 05:41:29 -04:00
if llen == 0 then
2022-10-17 12:56:31 -04:00
return true
end
2022-10-16 23:36:29 -04:00
2023-03-12 13:33:07 -04:00
local cron_key = 'cron_every_1s'
if self:is_working(cron_key) then
return true
end
2026-07-02 00:52:05 -04:00
local ready_ok = self:cronPre()
if not ready_ok then
return true
end
2022-10-17 13:08:05 -04:00
2026-07-02 01:15:12 -04:00
self:lock_working(cron_key)
2026-07-02 01:02:31 -04:00
2022-10-16 23:36:29 -04:00
local dbs = {}
2022-10-17 14:13:07 -04:00
local stmts = {}
2022-10-18 05:41:29 -04:00
local stat_fields = {}
2022-10-20 00:38:54 -04:00
local ip_stats = {}
local url_stats = {}
2026-07-02 01:15:12 -04:00
local rollback_sites = {}
2022-10-18 05:41:29 -04:00
local time_key = self:get_store_key()
2023-02-11 10:25:29 -05:00
2026-07-02 00:52:05 -04:00
for _, site_v in ipairs(sites) do
2022-10-18 05:41:29 -04:00
local input_sn = site_v["name"]
2022-10-17 12:56:31 -04:00
if self:is_migrating(input_sn) then
2026-07-02 01:15:12 -04:00
self:unlock_working(cron_key)
2022-10-17 12:56:31 -04:00
return true
end
2023-11-17 15:32:35 -05:00
if self:is_working('cron_init_stat') then
2026-07-02 01:15:12 -04:00
self:unlock_working(cron_key)
2023-11-17 15:32:35 -05:00
return true
end
2026-07-02 01:15:12 -04:00
local ok, db = pcall(function() return self:initDB(input_sn) end)
if not ok or not db then
self:D("initDB failed for " .. input_sn .. ": " .. tostring(db))
self:unlock_working(cron_key)
return true
end
2022-10-17 01:06:34 -04:00
2022-10-18 05:41:29 -04:00
stat_fields[input_sn] = {}
2026-07-02 01:15:12 -04:00
dbs[input_sn] = db
self:clean_stats(db, input_sn)
2022-10-17 14:13:07 -04:00
2026-07-02 01:15:12 -04:00
local ok, stmt = pcall(function()
return db:prepare[[INSERT INTO web_logs(
2022-10-17 14:13:07 -04:00
time, ip, domain, server_name, method, status_code, uri, body_length,
referer, user_agent, protocol, request_time, is_spider, request_headers, ip_list, client_port)
VALUES(:time, :ip, :domain, :server_name, :method, :status_code, :uri,
:body_length, :referer, :user_agent, :protocol, :request_time, :is_spider,
:request_headers, :ip_list, :client_port)]]
2026-07-02 01:15:12 -04:00
end)
if ok and stmt then
2026-07-02 00:52:05 -04:00
stmts[input_sn] = stmt
2022-10-20 01:10:40 -04:00
db:exec([[BEGIN TRANSACTION]])
2026-07-02 01:15:12 -04:00
else
2026-07-02 01:32:22 -04:00
-- self:D("prepare statement failed for " .. input_sn .. ": " .. tostring(stmt))
2026-07-02 01:15:12 -04:00
if db and db:isopen() then
db:close()
end
dbs[input_sn] = nil
self:unlock_working(cron_key)
return true
2022-10-17 01:06:34 -04:00
end
end
2022-10-16 23:36:29 -04:00
2026-07-02 01:15:12 -04:00
local has_error = false
2022-10-17 12:56:31 -04:00
2026-07-02 00:52:05 -04:00
for i = 1, llen do
local data = ngx.shared.mw_total:lpop(total_key)
2022-10-20 00:38:54 -04:00
if not data then
break
end
2022-10-17 00:12:17 -04:00
2026-07-02 01:15:12 -04:00
local ok, info = pcall(function() return json.decode(data) end)
if not ok then
self:D("json decode failed: " .. tostring(info))
ngx.shared.mw_total:rpush(total_key, data)
has_error = true
break
end
2022-10-20 00:38:54 -04:00
local input_sn = info['server_name']
local db = dbs[input_sn]
if not db then
2023-03-11 23:13:43 -05:00
ngx.shared.mw_total:rpush(total_key, data)
2026-07-02 01:15:12 -04:00
has_error = true
2022-10-20 00:38:54 -04:00
break
end
2022-10-18 05:41:29 -04:00
2026-07-02 00:52:05 -04:00
local stmt = stmts[input_sn]
if not stmt then
2023-11-21 08:59:24 -05:00
ngx.shared.mw_total:rpush(total_key, data)
2026-07-02 01:15:12 -04:00
has_error = true
2023-11-21 08:59:24 -05:00
break
end
2026-07-02 00:52:05 -04:00
local insert_ok = self:store_logs_line(db, stmt, input_sn, info)
2023-03-11 03:40:45 -05:00
if not insert_ok then
2023-03-11 23:13:43 -05:00
ngx.shared.mw_total:rpush(total_key, data)
2026-07-02 01:15:12 -04:00
rollback_sites[input_sn] = true
has_error = true
2023-03-11 03:40:45 -05:00
break
end
2022-10-18 05:41:29 -04:00
2026-07-02 00:52:05 -04:00
local log_kv = info["log_kv"]
local excluded = log_kv['excluded']
2022-10-20 00:38:54 -04:00
local stat_tmp_fields = info['stat_fields']
2022-10-18 05:41:29 -04:00
2026-07-02 00:52:05 -04:00
local stat_fields_is = stat_fields[input_sn]
for stf_k, stf_v in pairs(stat_tmp_fields) do
2022-10-20 00:38:54 -04:00
if excluded then
if stf_k == "spider_stat_fields" or stf_k == "client_stat_fields" then
break
2022-10-18 05:41:29 -04:00
end
2022-10-20 00:38:54 -04:00
end
2026-07-02 00:52:05 -04:00
local stf_is = stat_fields_is[stf_k]
if not stf_is then
stf_is = {}
stat_fields_is[stf_k] = stf_is
2022-10-20 00:38:54 -04:00
end
2026-07-02 00:52:05 -04:00
for sv_k, sv_v in pairs(stf_v) do
stf_is[sv_k] = (stf_is[sv_k] or 0) + sv_v
2022-10-20 00:38:54 -04:00
end
end
if not excluded then
2026-07-02 00:52:05 -04:00
local ip = log_kv['ip']
local body_length = log_kv["body_length"]
2022-10-20 00:38:54 -04:00
2026-07-02 00:52:05 -04:00
local ip_stats_sn = ip_stats[input_sn]
if not ip_stats_sn then
ip_stats_sn = {}
ip_stats[input_sn] = ip_stats_sn
2022-10-20 00:38:54 -04:00
end
2022-10-18 05:41:29 -04:00
2026-07-02 00:52:05 -04:00
local ip_stat = ip_stats_sn[ip]
if not ip_stat then
ip_stats_sn[ip] = {ip_num = 1, body_length = body_length}
2022-10-20 00:38:54 -04:00
else
2026-07-02 00:52:05 -04:00
ip_stat.ip_num = ip_stat.ip_num + 1
ip_stat.body_length = ip_stat.body_length + body_length
2022-10-20 00:38:54 -04:00
end
2026-07-02 00:52:05 -04:00
local url_stats_sn = url_stats[input_sn]
if not url_stats_sn then
url_stats_sn = {}
url_stats[input_sn] = url_stats_sn
2022-10-20 00:38:54 -04:00
end
2026-07-02 00:52:05 -04:00
local request_uri = log_kv["request_uri"]
2022-10-20 00:38:54 -04:00
local request_uri_md5 = ngx.md5(request_uri)
2026-07-02 00:52:05 -04:00
local url_stat = url_stats_sn[request_uri_md5]
if not url_stat then
url_stats_sn[request_uri_md5] = {url_num = 1, uri = request_uri, body_length = body_length}
2022-10-20 00:38:54 -04:00
else
2026-07-02 00:52:05 -04:00
url_stat.url_num = url_stat.url_num + 1
url_stat.body_length = url_stat.body_length + body_length
2022-10-16 12:58:20 -04:00
end
2022-10-16 12:44:04 -04:00
end
end
2022-10-16 14:04:01 -04:00
2026-07-02 00:52:05 -04:00
local save_day = config['global']["save_day"]
local now_date = os.date("*t")
local save_date_timestamp = os.time{year = now_date.year, month = now_date.month, day = now_date.day - save_day, hour = 0}
local delete_sql = "DELETE FROM web_logs WHERE time<" .. tostring(save_date_timestamp)
for _, site_v in ipairs(sites) do
2022-10-18 05:41:29 -04:00
local input_sn = site_v["name"]
2022-10-17 01:06:34 -04:00
2026-07-02 00:52:05 -04:00
local stmt = stmts[input_sn]
if stmt then
2026-07-02 01:15:12 -04:00
pcall(function() stmt:finalize() end)
2022-10-17 14:54:31 -04:00
end
2022-10-18 05:41:29 -04:00
local stat_fields_is = stat_fields[input_sn]
2022-10-17 14:54:31 -04:00
local db = dbs[input_sn]
2022-10-18 05:41:29 -04:00
2022-10-20 00:38:54 -04:00
if db then
2026-07-02 01:15:12 -04:00
if rollback_sites[input_sn] then
pcall(function() db:exec([[ROLLBACK]]) end)
else
for sti_k, sti_v in pairs(stat_fields_is) do
local vkk = ""
for sv_k, sv_v in pairs(sti_v) do
vkk = vkk .. sv_k .. "=" .. sv_k .. "+" .. sv_v .. ","
end
if vkk ~= "" then
vkk = string.sub(vkk, 1, string.len(vkk) - 1)
if sti_k == 'request_stat_fields' then
self:update_stat(db, "request_stat", time_key, vkk)
elseif sti_k == 'client_stat_fields' then
self:update_stat(db, "client_stat", time_key, vkk)
elseif sti_k == 'spider_stat_fields' then
self:update_stat(db, "spider_stat", time_key, vkk)
end
2026-07-02 00:52:05 -04:00
end
2022-10-20 00:38:54 -04:00
end
2022-10-18 05:41:29 -04:00
2026-07-02 01:15:12 -04:00
local local_ip_stats = ip_stats[input_sn]
if local_ip_stats then
for ip_addr, ip_val in pairs(local_ip_stats) do
self:update_statistics_ip(db, ip_addr, ip_val.ip_num, ip_val.body_length)
end
2022-10-20 00:38:54 -04:00
end
2022-10-18 05:41:29 -04:00
2026-07-02 01:15:12 -04:00
local local_url_stats = url_stats[input_sn]
if local_url_stats then
for url_md5, url_val in pairs(local_url_stats) do
self:update_statistics_uri(db, url_val.uri, url_md5, url_val.url_num, url_val.body_length)
end
2022-10-20 00:38:54 -04:00
end
2022-10-18 05:41:29 -04:00
2026-07-02 01:15:12 -04:00
db:exec(delete_sql)
pcall(function() db:execute([[COMMIT]]) end)
end
2022-10-17 00:17:37 -04:00
end
2022-10-17 14:54:31 -04:00
if db and db:isopen() then
2026-07-02 01:15:12 -04:00
pcall(function() db:close() end)
2022-10-16 14:30:36 -04:00
end
2022-10-17 14:54:31 -04:00
end
2022-10-16 14:45:18 -04:00
2022-10-16 14:04:01 -04:00
self:unlock_working(cron_key)
2022-10-16 12:44:04 -04:00
end
2023-11-21 06:08:39 -05:00
function timer_every_get_data_try()
local presult, err = pcall( function() timer_every_get_data() end)
if not presult then
self:D("debug cron error on :"..tostring(err))
return true
end
end
ngx.timer.every(0.5, timer_every_get_data_try)
2022-10-16 12:44:04 -04:00
end
function _M.store_logs_line(self, db, stmt, input_sn, info)
local logline = info['log_kv']
2026-07-02 00:52:05 -04:00
2025-08-22 12:53:47 -04:00
local excluded = logline["excluded"]
2026-07-02 00:52:05 -04:00
if excluded then
return true
end
2025-08-22 12:53:47 -04:00
2026-07-02 00:52:05 -04:00
local user_agent = logline["user_agent"]
2025-08-22 12:53:47 -04:00
if "table" == type(user_agent) then
user_agent = self:to_json(user_agent)
end
2025-08-22 12:53:58 -04:00
2026-07-02 00:52:05 -04:00
stmt:bind_names {
time = logline["time"],
ip = logline["ip"],
domain = logline["domain"],
server_name = logline["server_name"],
method = logline["method"],
status_code = logline["status_code"],
uri = logline["request_uri"],
body_length = logline["body_length"],
referer = logline["referer"],
user_agent = user_agent,
protocol = logline["protocol"],
request_time = logline["request_time"],
is_spider = logline["is_spider"],
request_headers = logline["request_headers"],
ip_list = logline["ip_list"],
client_port = logline["client_port"],
}
2022-10-16 12:44:04 -04:00
2026-07-02 00:52:05 -04:00
local res = stmt:step()
if tostring(res) == "5" then
2022-10-17 13:48:18 -04:00
stmt:reset()
2026-07-02 00:52:05 -04:00
return false
2022-10-16 12:44:04 -04:00
end
2026-07-02 00:52:05 -04:00
stmt:reset()
2022-10-16 12:44:04 -04:00
return true
end
2022-10-17 09:12:42 -04:00
function _M.statistics_ipc(self, input_sn, ip)
-- 判断IP是否重复的时间限定范围是请求的当前时间+24小时
local ipc = 0
local ip_token = input_sn..'_'..ip
if not cache:get(ip_token) then
ipc = 1
cache:set(ip_token,1, self:get_end_time())
end
return ipc
end
function _M.statistics_request(self, ip, is_spider, body_length)
-- 计算pv uv
local pvc = 0
local uvc = 0
2024-05-24 23:31:56 -04:00
local request_header = ngx.req.get_headers()
if not is_spider and ngx.status == 200 and body_length > 0 then
local ua = ''
if request_header['user-agent'] then
2025-08-22 12:50:28 -04:00
if "table" == type(request_header['user-agent']) then
ua = self:to_json(request_header['user-agent'])
else
ua = request_header['user-agent']
end
ua = string.lower(ua)
2024-05-24 23:31:56 -04:00
end
2022-10-17 09:12:42 -04:00
2024-05-24 23:31:56 -04:00
pvc = 1
if ua then
local today = ngx.today()
local uv_token = ngx.md5(ip .. ua .. today)
if not cache:get(uv_token) then
uvc = 1
cache:set(uv_token,1, self:get_end_time())
end
end
end
return pvc, uvc
end
2024-05-24 23:44:00 -04:00
-- 仅计算GET/HTML
2024-05-24 23:31:56 -04:00
function _M.statistics_request_old(self, ip, is_spider, body_length)
-- 计算pv uv
local pvc = 0
local uvc = 0
local method = ngx.req.get_method()
2022-10-17 09:12:42 -04:00
if not is_spider and method == 'GET' and ngx.status == 200 and body_length > 512 then
local ua = ''
if request_header['user-agent'] then
ua = string.lower(request_header['user-agent'])
end
out_header = ngx.resp.get_headers()
if out_header['content-type'] then
2022-10-20 01:20:54 -04:00
if string.find(out_header['content-type'], 'text/html', 1, true) then
2022-10-17 09:12:42 -04:00
pvc = 1
if request_header['user-agent'] then
if string.find(ua,'mozilla') then
2022-10-20 01:20:54 -04:00
local today = ngx.today()
2022-10-17 09:12:42 -04:00
local uv_token = ngx.md5(ip .. request_header['user-agent'] .. today)
if not cache:get(uv_token) then
uvc = 1
cache:set(uv_token,1, self:get_end_time())
end
end
end
end
end
end
return pvc, uvc
end
--------------------- db start ---------------------------
2022-10-18 05:41:29 -04:00
2022-10-20 00:38:54 -04:00
function _M.update_statistics_uri(self, db, uri, uri_md5, day_num, body_length)
local open_statistics_uri = config['global']["statistics_uri"]
if not open_statistics_uri then return true end
2026-07-02 01:15:12 -04:00
local insert_stmt = db:prepare("INSERT INTO uri_stat(uri_md5, uri) SELECT :uri_md5, :uri WHERE NOT EXISTS (SELECT uri_md5 FROM uri_stat WHERE uri_md5 = :uri_md5);")
2026-07-02 00:52:05 -04:00
insert_stmt:bind_names{uri_md5 = uri_md5, uri = uri}
insert_stmt:step()
2026-07-02 01:15:12 -04:00
insert_stmt:finalize()
local update_sql = "UPDATE uri_stat SET " .. day_column .. "=" .. day_column .. "+" .. day_num .. ", " .. flow_column .. "=" .. flow_column .. "+" .. body_length .. " WHERE uri_md5=\"" .. uri_md5 .. "\";"
db:exec(update_sql)
2022-10-20 00:38:54 -04:00
return true
end
2022-10-16 12:44:04 -04:00
function _M.statistics_uri(self, db, uri, uri_md5, body_length)
-- count the number of URI requests and traffic
local open_statistics_uri = config['global']["statistics_uri"]
if not open_statistics_uri then return true end
local stat_sql = nil
stat_sql = "INSERT INTO uri_stat(uri_md5,uri) SELECT \""..uri_md5.."\",\""..uri.."\" WHERE NOT EXISTS (SELECT uri_md5 FROM uri_stat WHERE uri_md5=\""..uri_md5.."\");"
local res, err = db:exec(stat_sql)
2022-10-17 11:29:57 -04:00
stat_sql = "UPDATE uri_stat SET "..day_column.."="..day_column.."+1,"..flow_column.."="..flow_column.."+"..body_length.." WHERE uri_md5=\""..uri_md5.."\";"
2022-10-16 12:44:04 -04:00
local res, err = db:exec(stat_sql)
return true
end
2022-10-20 00:38:54 -04:00
2026-07-02 00:52:05 -04:00
function _M.update_statistics_ip(self, db, ip, day_num, body_length)
2022-10-20 00:38:54 -04:00
local open_statistics_ip = config['global']["statistics_ip"]
if not open_statistics_ip then return true end
2026-07-02 01:15:12 -04:00
local insert_stmt = db:prepare("INSERT INTO ip_stat(ip) SELECT :ip WHERE NOT EXISTS (SELECT ip FROM ip_stat WHERE ip = :ip);")
2026-07-02 00:52:05 -04:00
insert_stmt:bind_names{ip = ip}
insert_stmt:step()
2026-07-02 01:15:12 -04:00
insert_stmt:finalize()
local update_sql = "UPDATE ip_stat SET " .. day_column .. "=" .. day_column .. "+" .. day_num .. ", " .. flow_column .. "=" .. flow_column .. "+" .. body_length .. " WHERE ip=\"" .. ip .. "\";"
db:exec(update_sql)
2022-10-20 00:38:54 -04:00
return true
end
2022-10-16 12:44:04 -04:00
function _M.statistics_ip(self, db, ip, body_length)
2026-07-02 00:52:05 -04:00
return self:update_statistics_ip(db, ip, 1, body_length)
2022-10-16 12:44:04 -04:00
end
2022-10-18 05:41:29 -04:00
function _M._update_stat_pre(self, db, stat_table, key)
2023-01-14 02:14:51 -05:00
local local_sql = string.format("INSERT INTO %s(time) SELECT :time WHERE NOT EXISTS(SELECT time FROM %s WHERE time=:time);", stat_table, stat_table)
local update_stat_stmt = db:prepare(local_sql)
2023-03-05 05:34:35 -05:00
if update_stat_stmt then
update_stat_stmt:bind_names{time=key}
update_stat_stmt:step()
update_stat_stmt:finalize()
return true
end
return false
2022-10-18 05:41:29 -04:00
end
function _M.update_stat_quick(self, db, stat_table, key,columns)
if not columns then return end
local update_sql = "UPDATE ".. stat_table .. " SET " .. columns .. " WHERE time=" .. key
return db:exec(update_sql)
end
2022-10-17 09:12:42 -04:00
2022-10-18 05:41:29 -04:00
function _M.update_stat(self, db, stat_table, key, columns)
2022-10-16 12:44:04 -04:00
if not columns then return end
2026-07-02 00:52:05 -04:00
2026-07-02 01:15:12 -04:00
local insert_stmt = db:prepare("INSERT INTO " .. stat_table .. "(time) SELECT :time WHERE NOT EXISTS(SELECT time FROM " .. stat_table .. " WHERE time=:time);")
2026-07-02 00:52:05 -04:00
insert_stmt:bind_names{time = key}
insert_stmt:step()
2026-07-02 01:15:12 -04:00
insert_stmt:finalize()
2026-07-02 00:52:05 -04:00
local update_sql = "UPDATE " .. stat_table .. " SET " .. columns .. " WHERE time=" .. key
2022-10-17 09:12:42 -04:00
return db:exec(update_sql)
2022-10-16 12:44:04 -04:00
end
2022-10-17 09:12:42 -04:00
--------------------- db end ---------------------------
2022-10-16 12:44:04 -04:00
-- debug func
function _M.D(self,msg)
if not debug_mode then return true end
local fp = io.open('{$SERVER_APP}/debug.log', 'ab')
if fp == nil then
return nil
end
local localtime = os.date("%Y-%m-%d %H:%M:%S")
if server_name then
fp:write(tostring(msg) .. "\n")
else
fp:write(localtime..":"..tostring(msg) .. "\n")
end
fp:flush()
fp:close()
return true
end
2022-10-17 01:44:35 -04:00
function _M.is_migrating(self, input_sn)
2022-10-16 12:44:04 -04:00
local file = io.open("{$SERVER_APP}/migrating", "rb")
if file then return true end
local file = io.open("{$SERVER_APP}/logs/"..input_sn.."/migrating", "rb")
if file then return true end
return false
end
2022-10-16 14:04:01 -04:00
function _M.is_working(self,sign)
local work_status = cache:get(sign.."_working")
2022-10-16 12:44:04 -04:00
if work_status ~= nil and work_status == true then
return true
end
return false
end
2022-10-16 14:04:01 -04:00
function _M.lock_working(self, sign)
local working_key = sign.."_working"
2022-10-16 12:44:04 -04:00
cache:set(working_key, true, 60)
end
2022-10-16 14:04:01 -04:00
function _M.unlock_working(self, sign)
local working_key = sign.."_working"
2022-10-16 12:44:04 -04:00
cache:set(working_key, false)
end
function _M.write_file(self, filename, body, mode)
local fp = io.open(filename, mode)
if fp == nil then
return nil
end
fp:write(body)
fp:flush()
fp:close()
return true
end
function _M.read_file_body(self, filename)
local fp = io.open(filename,'rb')
if not fp then
return nil
end
2026-07-02 00:52:05 -04:00
local fbody = fp:read("*a")
2022-10-16 12:44:04 -04:00
fp:close()
if fbody == '' then
return nil
end
return fbody
end
function _M.load_update_day(self, input_sn)
local _file = "{$SERVER_APP}/logs/"..input_sn.."/update_day.log"
return self:read_file_body(_file)
end
function _M.write_update_day(self, input_sn)
local _file = "{$SERVER_APP}/logs/"..input_sn.."/update_day.log"
return self:write_file(_file, today, "w")
end
2022-10-17 11:29:57 -04:00
function _M.clean_stats(self, db, input_sn)
-- 清空 uri,ip 汇总的信息[昨日]
local update_day = self:load_update_day(input_sn)
if not update_day or update_day ~= today then
local update_sql = "UPDATE uri_stat SET "..day_column.."=0,"..flow_column.."=0"
db:exec(update_sql)
update_sql = "UPDATE ip_stat SET "..day_column.."=0,"..flow_column.."=0"
db:exec(update_sql)
self:write_update_day(input_sn)
end
end
2022-10-16 12:44:04 -04:00
function _M.lpop(self)
local cache = ngx.shared.mw_total
return cache:lpop(total_key)
end
function _M.rpop(self)
local cache = ngx.shared.mw_total
return cache:rpop(total_key)
end
2022-10-17 09:12:42 -04:00
function _M.get_update_field(self, field, value)
return field.."="..field.."+"..tostring(value)
end
function _M.get_request_time(self)
local request_time = math.floor((ngx.now() - ngx.req.start_time()) * 1000)
if request_time == 0 then request_time = 1 end
return request_time
end
function _M.get_end_time(self)
local s_time = ngx.time()
local n_date = os.date("*t",s_time + 86400)
n_date.hour = 0
n_date.min = 0
n_date.sec = 0
local d_time = ngx.time(n_date)
return d_time - s_time
end
2026-07-02 00:52:05 -04:00
local spider_table = {
["baidu"] = 1,
["bing"] = 2,
["qh360"] = 3,
["google"] = 4,
["bytes"] = 5,
["sogou"] = 6,
["youdao"] = 7,
["soso"] = 8,
["dnspod"] = 9,
["yandex"] = 10,
["yisou"] = 11,
["other"] = 12,
["mpcrawler"] = 13,
["yahoo"] = 14,
["duckduckgo"] = 15
}
local spider_keywords = {
["baidu"] = "baidu",
["bytes"] = "bytes",
["360spider"] = "qh360",
["sogou"] = "sogou",
["soso"] = "soso",
["google"] = "google",
["bingbot"] = "bing",
["youdao"] = "youdao",
["dnspod"] = "dnspod",
["yandex"] = "yandex",
["yisou"] = "yisou",
["mpcrawler"] = "mpcrawler",
["yahoo"] = "yahoo",
["slurp"] = "other",
["duckduckgo"] = "other",
["semrush"] = "other",
["spider"] = "other",
["bot"] = "other",
["crawler"] = "other"
}
2022-10-17 09:12:42 -04:00
function _M.match_spider(self, ua)
local is_spider = false
local spider_name = ""
2026-07-02 00:52:05 -04:00
if not ua then
return false, "", 0
end
local lower_ua = string.lower(ua)
for kw, name in pairs(spider_keywords) do
if string.find(lower_ua, kw, 1, true) then
is_spider = true
spider_name = name
break
2022-10-17 09:12:42 -04:00
end
end
2026-07-02 00:52:05 -04:00
if is_spider then
2022-10-17 09:12:42 -04:00
return is_spider, spider_name, spider_table[spider_name]
end
2026-07-02 00:52:05 -04:00
2022-10-17 09:12:42 -04:00
return false, "", 0
end
2026-07-02 00:52:05 -04:00
local clients_map = {
["android"] = "android",
["iphone"] = "iphone",
["ipod"] = "iphone",
["ipad"] = "iphone",
["firefox"] = "firefox",
["msie"] = "msie",
["trident"] = "msie",
["360se"] = "qh360",
["360ee"] = "qh360",
["360browser"] = "qh360",
["qihoo"] = "qh360",
["theworld"] = "theworld",
["tencenttraveler"] = "tt",
["maxthon"] = "maxthon",
["opera"] = "opera",
["qqbrowser"] = "qq",
["ucweb"] = "uc",
["ubrowser"] = "uc",
["safari"] = "safari",
["chrome"] = "chrome",
["metasr"] = "metasr",
["2345explorer"] = "pc2345",
["edge"] = "edg",
["edg"] = "edg",
["windows"] = "windows",
["linux"] = "linux",
["macintosh"] = "mac",
["mobile"] = "mobile"
}
local mobile_keywords = {"mobile", "android", "iphone", "ipod", "ipad"}
local pc_browser_keywords = {
["360se"] = "qh360",
["360ee"] = "qh360",
["360browser"] = "qh360",
["qihoo"] = "qh360",
["theworld"] = "theworld",
["tencenttraveler"] = "tt",
["maxthon"] = "maxthon",
["opera"] = "opera",
["qqbrowser"] = "qq",
["ucweb"] = "uc",
["ubrowser"] = "uc",
["metasr"] = "metasr",
["2345explorer"] = "pc2345",
["edge"] = "edg",
["firefox"] = "firefox",
["chrome"] = "chrome",
["safari"] = "safari",
["msie"] = "msie",
["trident"] = "msie"
}
local os_keywords = {"windows", "linux", "macintosh"}
local machine_keywords = {"apachebench", "curl", "headlesschrome", "wget", "spider", "crawler", "scrapy", "zgrab", "python", "java"}
local function _match_client_table(self, ua)
local client_stat_fields = {}
2022-10-17 09:12:42 -04:00
if not ua then
return client_stat_fields
end
2026-07-02 00:52:05 -04:00
if "table" == type(ua) then
ua = self:to_json(ua)
end
local lower_ua = string.lower(ua)
local is_mobile = false
for _, kw in ipairs(mobile_keywords) do
if string.find(lower_ua, kw, 1, true) then
is_mobile = true
client_stat_fields["mobile"] = 1
if kw ~= "mobile" then
client_stat_fields[clients_map[kw]] = 1
2022-10-17 09:12:42 -04:00
end
2026-07-02 00:52:05 -04:00
break
2022-10-17 09:12:42 -04:00
end
end
2022-10-18 05:41:29 -04:00
2026-07-02 00:52:05 -04:00
if not is_mobile then
2022-10-18 05:41:29 -04:00
local cls_pc = nil
2026-07-02 00:52:05 -04:00
for kw, name in pairs(pc_browser_keywords) do
if string.find(lower_ua, kw, 1, true) then
cls_pc = name
break
2022-10-18 05:41:29 -04:00
end
end
2026-07-02 00:52:05 -04:00
2022-10-18 05:41:29 -04:00
if cls_pc then
2026-07-02 00:52:05 -04:00
client_stat_fields[cls_pc] = 1
2022-10-18 05:41:29 -04:00
else
2026-07-02 00:52:05 -04:00
local is_machine = false
for _, kw in ipairs(machine_keywords) do
if string.find(lower_ua, kw, 1, true) then
is_machine = true
break
end
end
if is_machine then
2022-10-18 05:41:29 -04:00
client_stat_fields["machine"] = 1
else
client_stat_fields["other"] = 1
end
end
2026-07-02 00:52:05 -04:00
for _, kw in ipairs(os_keywords) do
if string.find(lower_ua, kw, 1, true) then
client_stat_fields[clients_map[kw]] = 1
break
end
2022-10-18 05:41:29 -04:00
end
end
2026-07-02 00:52:05 -04:00
if string.find(lower_ua, "micromessenger", 1, true) then
2022-10-18 05:41:29 -04:00
client_stat_fields["weixin"] = 1
end
2026-07-02 00:52:05 -04:00
2022-10-18 05:41:29 -04:00
return client_stat_fields
end
2026-07-02 00:52:05 -04:00
function _M.match_client_arr(self, ua)
return _match_client_table(self, ua)
end
function _M.match_client(self, ua)
local client_stat_fields = _match_client_table(self, ua)
local fields_str = ""
for k, v in pairs(client_stat_fields) do
fields_str = fields_str .. "," .. self:get_update_field(k, v)
end
if fields_str ~= "" then
fields_str = string.sub(fields_str, 2)
end
return fields_str
end
2022-10-17 09:12:42 -04:00
function _M.get_client_ip(self)
local client_ip = "unknown"
local cdn = auto_config['cdn']
2023-02-27 06:35:52 -05:00
local request_header = ngx.req.get_headers()
2022-10-17 09:12:42 -04:00
if cdn == true then
for _,v in ipairs(auto_config['cdn_headers']) do
if request_header[v] ~= nil and request_header[v] ~= "" then
local ip_list = request_header[v]
client_ip = self:split(ip_list,',')[1]
break;
end
end
end
-- ipv6
if type(client_ip) == 'table' then client_ip = "" end
if client_ip ~= "unknown" and ngx.re.match(client_ip,"^([a-fA-F0-9]*):") then
return client_ip
end
-- ipv4
if not ngx.re.match(client_ip,"\\d+\\.\\d+\\.\\d+\\.\\d+") == nil or not self:is_ipaddr(client_ip) then
client_ip = ngx.var.remote_addr
if client_ip == nil then
client_ip = "unknown"
end
end
return client_ip
end
2022-10-16 12:44:04 -04:00
return _M