Files
samwafgo 64aaab16fa Feat log shared (#1007)
* fix: make archived log shard connections concurrency-safe and recoverable

* fix: build the WebSocket online table before its consumers start

* perf: cap stored payloads, index log lookups by IP and rule, batch IP tag writes

* fix: keep archived log shards readable when the live table gains a column

* feat: merge IP tag history when their database location changes

* refactor: move request payloads out of the access log into their own table

* feat: scope log database export to a time range and selected tiers

* feat: add an IP watchlist that full-captures watched IPs

* feat: split log storage into security events, access rows, and payload tiers

* feat: roll up requests per actor and path into daily analysis tables

* fix:actor key by ip

* feat: add a source and path analysis page over the daily rollups

* feat: exclude chosen source IPs from web log recording

* docs: add upgrade notes for the analysis page and log IP exclusion

* feat: derive the log partition from the time range and look up identifiers across all of them

* feat: add time partition verbs and the period naming they share

* feat: cut log archives per month and expire them by dropping partitions

* feat: manage log partitions, and answer why an IP has no logs

* fix: read archived SQLite log shards read-only and choose tiers by data

* fix: count the live log shard from access_log instead of a stale snapshot

* fix:export db bug

* fix: default backend keep-alive pool (512/128/90s) to stop connect-close churn under load

* chore: gen_swagger.bat outputs json only, syncs doc site, runs route test

* feat: pin web frontend version and embed it into the binary
2026-10-10 14:39:06 +08:00

62 lines
1.4 KiB
Go

package global
import (
"SamWaf/model"
"sync"
"time"
)
// GWAF_IP_WATCH 重点 IP 观察名单的内存缓存。写入路径每条日志都要问一次,不能逐条查库;
// 名单是人工维护的、条数很小,全量放内存,定期重载兜底,增删后由服务层调 Reload 立即生效。
var GWAF_IP_WATCH = &ipWatchCache{ips: map[string]int64{}}
const ipWatchReloadInterval = 30 * time.Second
type ipWatchCache struct {
mu sync.RWMutex
ips map[string]int64 // ip -> 到期 unix 秒
loadedAt time.Time
}
// Has 该 IP 当前是否在观察名单内(未到期)。DB 未初始化时一律视为不在。
func (c *ipWatchCache) Has(ip string) bool {
if ip == "" {
return false
}
c.reloadIfDue(false)
c.mu.RLock()
exp, ok := c.ips[ip]
c.mu.RUnlock()
return ok && exp > time.Now().Unix()
}
// Reload 名单增删后立即重载。
func (c *ipWatchCache) Reload() {
c.reloadIfDue(true)
}
func (c *ipWatchCache) reloadIfDue(force bool) {
if GWAF_LOCAL_DB == nil {
return
}
c.mu.RLock()
due := time.Since(c.loadedAt) > ipWatchReloadInterval
c.mu.RUnlock()
if !force && !due {
return
}
var rows []model.IPWatchlist
if err := GWAF_LOCAL_DB.Select("ip", "expire_at").
Where("expire_at > ?", time.Now().Unix()).Find(&rows).Error; err != nil {
return
}
m := make(map[string]int64, len(rows))
for _, r := range rows {
m[r.IP] = r.ExpireAt
}
c.mu.Lock()
c.ips = m
c.loadedAt = time.Now()
c.mu.Unlock()
}