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
2022-11-28 13:58:52 -05:00
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 p airs ( 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