mirror of
https://gitee.com/samwaf/SamWaf.git
synced 2026-10-29 17:23:13 +08:00
feat:add mysql
This commit is contained in:
@@ -32,7 +32,9 @@ func (t *JsonTime) UnmarshalJSON(data []byte) error {
|
||||
|
||||
func (t JsonTime) Value() (driver.Value, error) {
|
||||
tm := time.Time(t)
|
||||
//return tm.Format(constant.TimeLayout), nil
|
||||
if tm.IsZero() {
|
||||
return nil, nil // MySQL strict mode rejects 0000-00-00; use NULL for unset times
|
||||
}
|
||||
return tm, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -186,6 +186,22 @@ var (
|
||||
GWAF_PWD_STATDB = "3Y)(27EtO^tK8Bj~STAT" //加密
|
||||
GWAF_PWD_LOGDB = "3Y)(27EtO^tK8Bj~LOG" //加密
|
||||
|
||||
// Database driver: "sqlite" | "mysql" | "sqlserver" (default: sqlite)
|
||||
GWAF_DB_DRIVER string = "sqlite"
|
||||
|
||||
// MySQL connection config (populated from conf/config.yml database.mysql.*)
|
||||
GWAF_MYSQL_HOST string = "127.0.0.1"
|
||||
GWAF_MYSQL_PORT int = 3306
|
||||
GWAF_MYSQL_USER string = "samwaf"
|
||||
GWAF_MYSQL_PASSWORD string = ""
|
||||
GWAF_MYSQL_CHARSET string = "utf8mb4"
|
||||
GWAF_MYSQL_CORE_DB string = "samwaf_core"
|
||||
GWAF_MYSQL_LOG_DB string = "samwaf_log"
|
||||
GWAF_MYSQL_STATS_DB string = "samwaf_stats"
|
||||
GWAF_MYSQL_MAX_OPEN_CONNS int = 50
|
||||
GWAF_MYSQL_MAX_IDLE_CONNS int = 10
|
||||
GWAF_MYSQL_CONN_MAX_LIFETIME_MINUTES int = 60
|
||||
|
||||
//默认创建的账户和密码
|
||||
GWAF_DEFAULT_ACCOUNT string = "admin" //默认创建的账户
|
||||
GWAF_DEFAULT_ACCOUNT_PWD string = "admin868" //默认创建的密码
|
||||
|
||||
@@ -51,11 +51,12 @@ require (
|
||||
google.golang.org/grpc v1.77.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gorm.io/gorm v1.26.1
|
||||
gorm.io/gorm v1.30.0
|
||||
)
|
||||
|
||||
require (
|
||||
dario.cat/mergo v1.0.0 // indirect
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect
|
||||
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect
|
||||
@@ -93,6 +94,7 @@ require (
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.28.0 // indirect
|
||||
github.com/go-sql-driver/mysql v1.8.1 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/goccy/go-yaml v1.18.0 // indirect
|
||||
@@ -165,6 +167,7 @@ require (
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gorm.io/driver/mysql v1.6.0 // indirect
|
||||
rsc.io/binaryregexp v0.2.0 // indirect
|
||||
)
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
||||
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
@@ -177,6 +179,8 @@ github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91
|
||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||
github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688=
|
||||
github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU=
|
||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
@@ -674,8 +678,12 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg=
|
||||
gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo=
|
||||
gorm.io/gorm v1.26.1 h1:ghB2gUI9FkS46luZtn6DLZ0f6ooBJ5IbVej2ENFDjRw=
|
||||
gorm.io/gorm v1.26.1/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
|
||||
gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs=
|
||||
gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE=
|
||||
|
||||
+47
-47
@@ -3,55 +3,55 @@ package innerbean
|
||||
import "strings"
|
||||
|
||||
type WebLog struct {
|
||||
WafInnerDFlag string `json:"waf_inner_dflag"` //日志队列处理方式
|
||||
HOST string `json:"host"`
|
||||
URL string `json:"url"`
|
||||
RawQuery string `json:"raw_query"` //原始URL查询
|
||||
REFERER string `json:"referer"`
|
||||
USER_AGENT string `json:"user_agent"`
|
||||
METHOD string `json:"method"`
|
||||
HEADER string `json:"header"`
|
||||
SRC_IP string `json:"src_ip"`
|
||||
SRC_PORT string `json:"src_port"`
|
||||
COUNTRY string `json:"country"`
|
||||
PROVINCE string `json:"province"`
|
||||
CITY string `json:"city"`
|
||||
CREATE_TIME string `gorm:"index:idx_weblog_time" json:"create_time"`
|
||||
WafInnerDFlag string `gorm:"size:10" json:"waf_inner_dflag"` //日志队列处理方式
|
||||
HOST string `gorm:"size:255" json:"host"`
|
||||
URL string `gorm:"type:text" json:"url"`
|
||||
RawQuery string `gorm:"type:text" json:"raw_query"` //原始URL查询
|
||||
REFERER string `gorm:"type:text" json:"referer"`
|
||||
USER_AGENT string `gorm:"size:500" json:"user_agent"`
|
||||
METHOD string `gorm:"size:20" json:"method"`
|
||||
HEADER string `gorm:"type:text" json:"header"`
|
||||
SRC_IP string `gorm:"size:64" json:"src_ip"`
|
||||
SRC_PORT string `gorm:"size:10" json:"src_port"`
|
||||
COUNTRY string `gorm:"size:100" json:"country"`
|
||||
PROVINCE string `gorm:"size:100" json:"province"`
|
||||
CITY string `gorm:"size:100" json:"city"`
|
||||
CREATE_TIME string `gorm:"size:32;index:idx_weblog_time" json:"create_time"`
|
||||
CONTENT_LENGTH int64 `json:"content_length"`
|
||||
RES_CONTENT_LENGTH int64 `json:"res_content_length"` //响应内容大小(字节)
|
||||
COOKIES string `json:"cookies"`
|
||||
BODY string `json:"body"`
|
||||
REQ_UUID string `json:"req_uuid"`
|
||||
USER_CODE string `json:"user_code" gorm:"index"`
|
||||
TenantId string `json:"tenant_id" gorm:"index"` //租户ID(主要键)
|
||||
HOST_CODE string `json:"host_code" ` //主机ID (主要键)
|
||||
Day int `json:"day"` //日 (主要键)
|
||||
ACTION string `json:"action"`
|
||||
RULE string `json:"rule"`
|
||||
STATUS string `json:"status"` //状态
|
||||
STATUS_CODE int `json:"status_code"` //状态编码
|
||||
RES_BODY string `json:"res_body"` //返回信息
|
||||
POST_FORM string `json:"post_form"` //提交的表单数据
|
||||
TASK_FLAG int `json:"task_flag" gorm:"default:-1;index"` //任务处理标记 -1 等待处理;1 可以进行处理;2 处理完毕
|
||||
UNIX_ADD_TIME int64 `json:"unix_add_time" gorm:"index"` //添加日期unix
|
||||
RISK_LEVEL int `json:"risk_level"` //危险等级 0:正常 1:轻微 2:有害 3:严重 4:特别严重
|
||||
GUEST_IDENTIFICATION string `json:"guest_identification"` //访客身份识别
|
||||
IsBot int `json:"is_bot"` //是否是机器人 0 不是机器人 1 机器人
|
||||
TimeSpent int64 `json:"time_spent"` //用时
|
||||
NetSrcIp string `json:"net_src_ip"` //获取的原始IP
|
||||
SrcByteBody []byte `json:"src_byte_body"` //原始body信息
|
||||
SrcByteResBody []byte `json:"src_byte_res_body"` //返回body bytes信息
|
||||
WebLogVersion int `json:"web_log_version"` //日志版本信息早期的是空和0,后期实时增加
|
||||
Scheme string `json:"scheme"` //HTTP 协议
|
||||
SrcURL []byte `json:"src_url"` //原始url信息
|
||||
PreCheckCost int64 `json:"pre_check_cost"` // 前置检查耗时(ms)
|
||||
ForwardCost int64 `json:"forward_cost"` // 转发耗时(ms)
|
||||
BackendCheckCost int64 `json:"backend_check_cost"` // 后端处理耗时(ms)
|
||||
ResHeader string `json:"res_header"` // 返回header情况
|
||||
BodyHash string `json:"body_hash"` // body hash值
|
||||
LogOnlyMode int `json:"log_only_mode"` //是否只记录日志 1 是 0 不是
|
||||
IsBalance int `json:"is_balance"` //是否是负载均衡 1 是 0 不是
|
||||
BalanceInfo string `json:"balance_info"` //负载均衡IP端口信息
|
||||
COOKIES string `gorm:"type:text" json:"cookies"`
|
||||
BODY string `gorm:"type:text" json:"body"`
|
||||
REQ_UUID string `gorm:"size:64" json:"req_uuid"`
|
||||
USER_CODE string `gorm:"size:64;index" json:"user_code"`
|
||||
TenantId string `gorm:"size:64;index" json:"tenant_id"` //租户ID(主要键)
|
||||
HOST_CODE string `gorm:"size:64" json:"host_code"` //主机ID (主要键)
|
||||
Day int `json:"day"` //日 (主要键)
|
||||
ACTION string `gorm:"size:100" json:"action"`
|
||||
RULE string `gorm:"type:text" json:"rule"`
|
||||
STATUS string `gorm:"size:50" json:"status"` //状态
|
||||
STATUS_CODE int `json:"status_code"` //状态编码
|
||||
RES_BODY string `gorm:"type:text" json:"res_body"` //返回信息
|
||||
POST_FORM string `gorm:"type:text" json:"post_form"` //提交的表单数据
|
||||
TASK_FLAG int `json:"task_flag" gorm:"default:-1;index"` //任务处理标记 -1 等待处理;1 可以进行处理;2 处理完毕
|
||||
UNIX_ADD_TIME int64 `json:"unix_add_time" gorm:"index"` //添加日期unix
|
||||
RISK_LEVEL int `json:"risk_level"` //危险等级 0:正常 1:轻微 2:有害 3:严重 4:特别严重
|
||||
GUEST_IDENTIFICATION string `gorm:"column:guest_identification;size:191" json:"guest_identification"` //访客身份识别
|
||||
IsBot int `json:"is_bot"` //是否是机器人 0 不是机器人 1 机器人
|
||||
TimeSpent int64 `json:"time_spent"` //用时
|
||||
NetSrcIp string `gorm:"size:64" json:"net_src_ip"` //获取的原始IP
|
||||
SrcByteBody []byte `json:"src_byte_body"` //原始body信息
|
||||
SrcByteResBody []byte `json:"src_byte_res_body"` //返回body bytes信息
|
||||
WebLogVersion int `json:"web_log_version"` //日志版本信息早期的是空和0,后期实时增加
|
||||
Scheme string `gorm:"size:20" json:"scheme"` //HTTP 协议
|
||||
SrcURL []byte `json:"src_url"` //原始url信息
|
||||
PreCheckCost int64 `json:"pre_check_cost"` // 前置检查耗时(ms)
|
||||
ForwardCost int64 `json:"forward_cost"` // 转发耗时(ms)
|
||||
BackendCheckCost int64 `json:"backend_check_cost"` // 后端处理耗时(ms)
|
||||
ResHeader string `gorm:"type:text" json:"res_header"` // 返回header情况
|
||||
BodyHash string `gorm:"size:100" json:"body_hash"` // body hash值
|
||||
LogOnlyMode int `json:"log_only_mode"` //是否只记录日志 1 是 0 不是
|
||||
IsBalance int `json:"is_balance"` //是否是负载均衡 1 是 0 不是
|
||||
BalanceInfo string `gorm:"size:255" json:"balance_info"` //负载均衡IP端口信息
|
||||
}
|
||||
|
||||
// GetHeaderValue 从HEADER字段中提取指定header的值
|
||||
|
||||
+4
-4
@@ -8,10 +8,10 @@ IP Tag 信息
|
||||
*/
|
||||
type IPTag struct {
|
||||
baseorm.BaseOrm
|
||||
IP string `json:"ip"` //ip
|
||||
IPTag string `json:"ip_tag"`
|
||||
Cnt int64 `json:"cnt"` //触发次数
|
||||
Remarks string `json:"remarks"` //备注
|
||||
IP string `gorm:"size:64" json:"ip"` //ip
|
||||
IPTag string `gorm:"size:255" json:"ip_tag"`
|
||||
Cnt int64 `json:"cnt"` //触发次数
|
||||
Remarks string `gorm:"size:500" json:"remarks"` //备注
|
||||
}
|
||||
type AttackIPTag struct {
|
||||
TenantID string `json:"tenant_id"`
|
||||
|
||||
+13
-13
@@ -6,24 +6,24 @@ import (
|
||||
|
||||
type Account struct {
|
||||
baseorm.BaseOrm
|
||||
LoginAccount string `json:"login_account"` //登录账号
|
||||
LoginPassword string `json:"login_password"` //密码md5加密
|
||||
Role string `json:"role"` //登录角色
|
||||
Status int `json:"status"` //状态
|
||||
Remarks string `json:"remarks"` //备注
|
||||
LoginAccount string `gorm:"size:100" json:"login_account"` //登录账号
|
||||
LoginPassword string `gorm:"size:255" json:"login_password"` //密码md5加密
|
||||
Role string `gorm:"size:50" json:"role"` //登录角色
|
||||
Status int `json:"status"` //状态
|
||||
Remarks string `gorm:"size:500" json:"remarks"` //备注
|
||||
}
|
||||
|
||||
type AccountLog struct {
|
||||
baseorm.BaseOrm
|
||||
LoginAccount string `json:"login_account"` //登录账号
|
||||
OpType string `json:"op_type"` //操作类型
|
||||
OpContent string `json:"op_content"` //操作内容
|
||||
LoginAccount string `gorm:"size:100" json:"login_account"` //登录账号
|
||||
OpType string `gorm:"size:100" json:"op_type"` //操作类型
|
||||
OpContent string `gorm:"type:text" json:"op_content"` //操作内容
|
||||
}
|
||||
type TokenInfo struct {
|
||||
baseorm.BaseOrm
|
||||
LoginAccount string `json:"login_account"` //登录账号
|
||||
LoginIp string `json:"login_ip"` //登录IP
|
||||
AccessToken string `json:"access_token" crypto:"aes"` //访问码
|
||||
DeviceFingerprint string `json:"device_fingerprint"` //设备指纹
|
||||
LoginType string `json:"login_type"` //登录类型 web/mobile
|
||||
LoginAccount string `gorm:"size:100" json:"login_account"` //登录账号
|
||||
LoginIp string `gorm:"size:64" json:"login_ip"` //登录IP
|
||||
AccessToken string `gorm:"type:text" json:"access_token" crypto:"aes"` //访问码
|
||||
DeviceFingerprint string `gorm:"size:255" json:"device_fingerprint"` //设备指纹
|
||||
LoginType string `gorm:"size:50" json:"login_type"` //登录类型 web/mobile
|
||||
}
|
||||
|
||||
+7
-7
@@ -6,14 +6,14 @@ import (
|
||||
|
||||
type IPAllowList struct {
|
||||
baseorm.BaseOrm
|
||||
HostCode string `json:"host_code"` //网站唯一码(主要键)
|
||||
Ip string `json:"ip"` //白名单ip
|
||||
Remarks string `json:"remarks"` //备注
|
||||
HostCode string `gorm:"size:64" json:"host_code"` //网站唯一码(主要键)
|
||||
Ip string `gorm:"size:64" json:"ip"` //白名单ip
|
||||
Remarks string `gorm:"size:500" json:"remarks"` //备注
|
||||
}
|
||||
type URLAllowList struct {
|
||||
baseorm.BaseOrm
|
||||
HostCode string `json:"host_code"` //网站唯一码(主要键)
|
||||
CompareType string `json:"compare_type"` //判断类型,包含、开始、结束、完全匹配
|
||||
Url string `json:"url"` //请求地址
|
||||
Remarks string `json:"remarks"` //备注
|
||||
HostCode string `gorm:"size:64" json:"host_code"` //网站唯一码(主要键)
|
||||
CompareType string `gorm:"size:50" json:"compare_type"` //判断类型,包含、开始、结束、完全匹配
|
||||
Url string `gorm:"type:text" json:"url"` //请求地址
|
||||
Remarks string `gorm:"size:500" json:"remarks"` //备注
|
||||
}
|
||||
|
||||
+8
-8
@@ -10,15 +10,15 @@ import "SamWaf/model/baseorm"
|
||||
|
||||
type AntiCC struct {
|
||||
baseorm.BaseOrm
|
||||
HostCode string `json:"host_code" gorm:"column:host_code"`
|
||||
HostCode string `json:"host_code" gorm:"column:host_code;size:64"`
|
||||
Rate int `json:"rate" gorm:"column:rate"`
|
||||
Limit int `json:"limit" gorm:"column:limit"`
|
||||
LockIPMinutes int `json:"lock_ip_minutes" gorm:"column:lock_ip_minutes"`
|
||||
LimitMode string `json:"limit_mode" gorm:"column:limit_mode"` // "rate" 或 "window"
|
||||
IPMode string `json:"ip_mode" gorm:"column:ip_mode"` // "nic" 网卡模式 或 "proxy" 代理模式
|
||||
Url string `json:"url"`
|
||||
IsEnableRule bool `json:"is_enable_rule" gorm:"column:is_enable_rule"` //是否启动规则
|
||||
RuleContent string `json:"rule_content" gorm:"column:rule_content"` //规则内容
|
||||
SkipGlobalCC bool `json:"skip_global_cc" gorm:"column:skip_global_cc"` //命中局部CC规则后跳过全局CC检测
|
||||
Remarks string `json:"remarks"` //备注
|
||||
LimitMode string `json:"limit_mode" gorm:"column:limit_mode;size:20"` // "rate" 或 "window"
|
||||
IPMode string `json:"ip_mode" gorm:"column:ip_mode;size:20"` // "nic" 网卡模式 或 "proxy" 代理模式
|
||||
Url string `gorm:"type:text" json:"url"`
|
||||
IsEnableRule bool `json:"is_enable_rule" gorm:"column:is_enable_rule"` //是否启动规则
|
||||
RuleContent string `json:"rule_content" gorm:"column:rule_content;type:text"` //规则内容
|
||||
SkipGlobalCC bool `json:"skip_global_cc" gorm:"column:skip_global_cc"` //命中局部CC规则后跳过全局CC检测
|
||||
Remarks string `gorm:"size:500" json:"remarks"` //备注
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
package model
|
||||
|
||||
type Base_config struct {
|
||||
Id int `gorm:"primary_key" json:" - "` //
|
||||
Name string `json:"name"` //
|
||||
Value string `json:"value"` //
|
||||
}
|
||||
@@ -7,9 +7,9 @@ import "SamWaf/customtype"
|
||||
base orm
|
||||
*/
|
||||
type BaseOrm struct {
|
||||
Id string `gorm:"primary_key" json:"id"` //
|
||||
USER_CODE string `json:"user_code"` // 用户码(主要键)
|
||||
Tenant_ID string `json:"tenant_id"` //租户ID
|
||||
CREATE_TIME customtype.JsonTime `json:"create_time"` //创建时间
|
||||
UPDATE_TIME customtype.JsonTime `json:"update_time"` //更新时间
|
||||
Id string `gorm:"primaryKey;size:64" json:"id"` //
|
||||
USER_CODE string `gorm:"size:64" json:"user_code"` // 用户码(主要键)
|
||||
Tenant_ID string `gorm:"size:64" json:"tenant_id"` //租户ID
|
||||
CREATE_TIME customtype.JsonTime `json:"create_time"` //创建时间
|
||||
UPDATE_TIME customtype.JsonTime `json:"update_time"` //更新时间
|
||||
}
|
||||
|
||||
+9
-9
@@ -5,13 +5,13 @@ import "SamWaf/model/baseorm"
|
||||
// BatchTask 批量任务
|
||||
type BatchTask struct {
|
||||
baseorm.BaseOrm
|
||||
BatchTaskName string `json:"batch_task_name"` //任务名
|
||||
BatchType string `json:"batch_type"` //任务类型
|
||||
BatchHostCode string `json:"batch_host_code"` //网站唯一码 是否绑定到某个主机上
|
||||
BatchSourceType string `json:"batch_source_type"` //来源类型(local,url)
|
||||
BatchTriggerType string `json:"batch_trigger_type"` //触发类型 定时任务 cron ,手动任务 manual
|
||||
BatchSource string `json:"batch_source"` //来源内容 路径或者实际的url内容
|
||||
BatchExecuteMethod string `json:"batch_execute_method"` //任务执行方式 追加,覆盖
|
||||
BatchExtraConfig string `json:"batch_extra_config"` //额外配置字段(JSON字符串)
|
||||
Remark string `json:"remark"` //备注
|
||||
BatchTaskName string `gorm:"size:255" json:"batch_task_name"` //任务名
|
||||
BatchType string `gorm:"size:50" json:"batch_type"` //任务类型
|
||||
BatchHostCode string `gorm:"size:64" json:"batch_host_code"` //网站唯一码 是否绑定到某个主机上
|
||||
BatchSourceType string `gorm:"size:50" json:"batch_source_type"` //来源类型(local,url)
|
||||
BatchTriggerType string `gorm:"size:50" json:"batch_trigger_type"` //触发类型 定时任务 cron ,手动任务 manual
|
||||
BatchSource string `gorm:"type:text" json:"batch_source"` //来源内容 路径或者实际的url内容
|
||||
BatchExecuteMethod string `gorm:"size:100" json:"batch_execute_method"` //任务执行方式 追加,覆盖
|
||||
BatchExtraConfig string `gorm:"type:text" json:"batch_extra_config"` //额外配置字段(JSON字符串)
|
||||
Remark string `gorm:"size:500" json:"remark"` //备注
|
||||
}
|
||||
|
||||
@@ -5,11 +5,11 @@ import "SamWaf/model/baseorm"
|
||||
// BlockingPage 自定义拦截模板界面
|
||||
type BlockingPage struct {
|
||||
baseorm.BaseOrm
|
||||
BlockingPageName string `json:"blocking_page_name"` //自定义拦截模板页面名称
|
||||
BlockingType string `json:"blocking_type"` //自定义类型 被拦截
|
||||
AttackType string `json:"attack_type"` //攻击类型 如: cc_attack, sensitive_word, sql_injection等
|
||||
HostCode string `json:"host_code"` //适用于某个网站唯一码
|
||||
ResponseCode string `json:"response_code"` //响应代码 默认403
|
||||
ResponseHeader string `json:"response_header"` //响应Header头信息(JSON)
|
||||
ResponseContent string `json:"response_content"` //响应内容
|
||||
BlockingPageName string `gorm:"size:255" json:"blocking_page_name"` //自定义拦截模板页面名称
|
||||
BlockingType string `gorm:"size:50" json:"blocking_type"` //自定义类型 被拦截
|
||||
AttackType string `gorm:"size:100" json:"attack_type"` //攻击类型 如: cc_attack, sensitive_word, sql_injection等
|
||||
HostCode string `gorm:"size:64" json:"host_code"` //适用于某个网站唯一码
|
||||
ResponseCode string `gorm:"size:10" json:"response_code"` //响应代码 默认403
|
||||
ResponseHeader string `gorm:"type:text" json:"response_header"` //响应Header头信息(JSON)
|
||||
ResponseContent string `gorm:"type:text" json:"response_content"` //响应内容
|
||||
}
|
||||
|
||||
+7
-7
@@ -6,15 +6,15 @@ import (
|
||||
|
||||
type IPBlockList struct {
|
||||
baseorm.BaseOrm
|
||||
HostCode string `json:"host_code"` //网站唯一码(主要键)
|
||||
Ip string `json:"ip"` //限制ip
|
||||
Remarks string `json:"remarks"` //备注
|
||||
HostCode string `gorm:"size:64" json:"host_code"` //网站唯一码(主要键)
|
||||
Ip string `gorm:"size:64" json:"ip"` //限制ip
|
||||
Remarks string `gorm:"size:500" json:"remarks"` //备注
|
||||
}
|
||||
|
||||
type URLBlockList struct {
|
||||
baseorm.BaseOrm
|
||||
HostCode string `json:"host_code"` //网站唯一码(主要键)
|
||||
CompareType string `json:"compare_type" form:"compare_type"` //对比方式
|
||||
Url string `json:"url"` //限制请求地址
|
||||
Remarks string `json:"remarks"` //备注
|
||||
HostCode string `gorm:"size:64" json:"host_code"` //网站唯一码(主要键)
|
||||
CompareType string `gorm:"size:50" json:"compare_type" form:"compare_type"` //对比方式
|
||||
Url string `gorm:"type:text" json:"url"` //限制请求地址
|
||||
Remarks string `gorm:"size:500" json:"remarks"` //备注
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import "SamWaf/model/baseorm"
|
||||
|
||||
type CaServerInfo struct {
|
||||
baseorm.BaseOrm
|
||||
CaServerName string `json:"ca_server_name"` //CA服务器名称
|
||||
CaServerAddress string `json:"ca_server_address"` //CA服务器地址
|
||||
Remarks string `json:"remarks"` //备注
|
||||
CaServerName string `gorm:"size:255" json:"ca_server_name"` //CA服务器名称
|
||||
CaServerAddress string `gorm:"size:500" json:"ca_server_address"` //CA服务器地址
|
||||
Remarks string `gorm:"size:500" json:"remarks"` //备注
|
||||
}
|
||||
|
||||
+9
-9
@@ -5,13 +5,13 @@ import "SamWaf/model/baseorm"
|
||||
// 缓存规则
|
||||
type CacheRule struct {
|
||||
baseorm.BaseOrm
|
||||
HostCode string `json:"host_code" gorm:"column:host_code"` // 主机代码
|
||||
RuleName string `json:"rule_name" gorm:"column:rule_name"` // 规则名称
|
||||
RuleType int `json:"rule_type" gorm:"column:rule_type"` // 规则类型:1-后缀匹配 ,2-指定目录,3-指定文件
|
||||
RuleContent string `json:"rule_content" gorm:"column:rule_content"` // 规则内容
|
||||
ParamType int `json:"param_type" gorm:"column:param_type"` // 参数处理:1-忽略参数,2-完整参数
|
||||
CacheTime int `json:"cache_time" gorm:"column:cache_time"` // 缓存时间(秒):0-不缓存
|
||||
Priority int `json:"priority" gorm:"column:priority"` // 优先级:数字越大优先级越高
|
||||
RequestMethod string `json:"request_method" gorm:"column:request_method"` // 请求方式:GET;HEAD;POST等,多个用分号分隔
|
||||
Remarks string `json:"remarks" gorm:"column:remarks"` // 备注
|
||||
HostCode string `json:"host_code" gorm:"column:host_code;size:64"` // 主机代码
|
||||
RuleName string `json:"rule_name" gorm:"column:rule_name;size:255"` // 规则名称
|
||||
RuleType int `json:"rule_type" gorm:"column:rule_type"` // 规则类型:1-后缀匹配 ,2-指定目录,3-指定文件
|
||||
RuleContent string `json:"rule_content" gorm:"column:rule_content;type:text"` // 规则内容
|
||||
ParamType int `json:"param_type" gorm:"column:param_type"` // 参数处理:1-忽略参数,2-完整参数
|
||||
CacheTime int `json:"cache_time" gorm:"column:cache_time"` // 缓存时间(秒):0-不缓存
|
||||
Priority int `json:"priority" gorm:"column:priority"` // 优先级:数字越大优先级越高
|
||||
RequestMethod string `json:"request_method" gorm:"column:request_method;size:20"` // 请求方式:GET;HEAD;POST等,多个用分号分隔
|
||||
Remarks string `json:"remarks" gorm:"column:remarks;size:500"` // 备注
|
||||
}
|
||||
|
||||
+12
-12
@@ -11,16 +11,16 @@ import (
|
||||
*/
|
||||
type Center struct {
|
||||
baseorm.BaseOrm
|
||||
ClientServerName string `json:"client_server_name"` // 客户端-自定义名称
|
||||
ClientUserCode string `json:"client_user_code"` // 客户端-用户码
|
||||
ClientTenantId string `json:"client_tenant_id"` // 客户端-租户ID
|
||||
ClientToken string `json:"client_token"` // 客户端-访问密钥
|
||||
ClientSsl string `json:"client_ssl"` // 客户端-是否https
|
||||
ClientIP string `json:"client_ip"` //客户端 ip
|
||||
ClientPort string `json:"client_port"` //客户端 port
|
||||
ClientNewVersion string `json:"client_new_version"` //客户端 版本号
|
||||
ClientNewVersionDesc string `json:"client_new_version_desc"` //客户端 版本描述
|
||||
ClientSystemType string `json:"client_system_type"` //操作系统类型
|
||||
LastVisitTime customtype.JsonTime `json:"last_visit_time"` //上次访问时间
|
||||
Remarks string `json:"remarks"` //备注
|
||||
ClientServerName string `gorm:"size:255" json:"client_server_name"` // 客户端-自定义名称
|
||||
ClientUserCode string `gorm:"size:64" json:"client_user_code"` // 客户端-用户码
|
||||
ClientTenantId string `gorm:"size:64" json:"client_tenant_id"` // 客户端-租户ID
|
||||
ClientToken string `gorm:"size:500" json:"client_token"` // 客户端-访问密钥
|
||||
ClientSsl string `gorm:"size:10" json:"client_ssl"` // 客户端-是否https
|
||||
ClientIP string `gorm:"size:64" json:"client_ip"` //客户端 ip
|
||||
ClientPort string `gorm:"size:10" json:"client_port"` //客户端 port
|
||||
ClientNewVersion string `gorm:"size:64" json:"client_new_version"` //客户端 版本号
|
||||
ClientNewVersionDesc string `gorm:"type:text" json:"client_new_version_desc"` //客户端 版本描述
|
||||
ClientSystemType string `gorm:"size:50" json:"client_system_type"` //操作系统类型
|
||||
LastVisitTime customtype.JsonTime `json:"last_visit_time"` //上次访问时间
|
||||
Remarks string `gorm:"size:500" json:"remarks"` //备注
|
||||
}
|
||||
|
||||
@@ -13,16 +13,16 @@ import (
|
||||
// - "core" → GWAF_LOCAL_DB(核心库,预留)
|
||||
type DataRetentionPolicy struct {
|
||||
baseorm.BaseOrm
|
||||
TableName string `json:"table_name"` // 目标表名,如 stats_ip_days
|
||||
DbType string `json:"db_type"` // 归属库类型:stats / log / core
|
||||
RetainDays int64 `json:"retain_days"` // 保留天数,0=不按天清理
|
||||
RetainRows int64 `json:"retain_rows"` // 保留行数,0=不按行清理
|
||||
DayField string `json:"day_field"` // 天数清理依赖的字段名,如 "day" 或 "create_time"
|
||||
DayFieldType string `json:"day_field_type"` // 天数字段类型:int_day(YYYYMMDD整数) / datetime(时间类型)
|
||||
RowOrderField string `json:"row_order_field"` // 行数清理的排序字段,如 "day"、"update_time"、"cnt"
|
||||
RowOrderDir string `json:"row_order_dir"` // 排序方向:DESC=保留值最大的(最新),ASC=保留值最小的
|
||||
CleanEnabled int64 `json:"clean_enabled"` // 1=启用 0=禁用
|
||||
LastCleanTime customtype.JsonTime `json:"last_clean_time"` // 上次清理时间
|
||||
LastCleanRows int64 `json:"last_clean_rows"` // 上次清理的行数
|
||||
Remarks string `json:"remarks"` // 备注
|
||||
TableName string `gorm:"size:100" json:"table_name"` // 目标表名,如 stats_ip_days
|
||||
DbType string `gorm:"size:20" json:"db_type"` // 归属库类型:stats / log / core
|
||||
RetainDays int64 `json:"retain_days"` // 保留天数,0=不按天清理
|
||||
RetainRows int64 `json:"retain_rows"` // 保留行数,0=不按行清理
|
||||
DayField string `gorm:"size:100" json:"day_field"` // 天数清理依赖的字段名,如 "day" 或 "create_time"
|
||||
DayFieldType string `gorm:"size:50" json:"day_field_type"` // 天数字段类型:int_day(YYYYMMDD整数) / datetime(时间类型)
|
||||
RowOrderField string `gorm:"size:100" json:"row_order_field"` // 行数清理的排序字段,如 "day"、"update_time"、"cnt"
|
||||
RowOrderDir string `gorm:"size:20" json:"row_order_dir"` // 排序方向:DESC=保留值最大的(最新),ASC=保留值最小的
|
||||
CleanEnabled int64 `json:"clean_enabled"` // 1=启用 0=禁用
|
||||
LastCleanTime customtype.JsonTime `json:"last_clean_time"` // 上次清理时间
|
||||
LastCleanRows int64 `json:"last_clean_rows"` // 上次清理的行数
|
||||
Remarks string `gorm:"size:500" json:"remarks"` // 备注
|
||||
}
|
||||
|
||||
+3
-3
@@ -10,7 +10,7 @@ import (
|
||||
*/
|
||||
type DelayMsg struct {
|
||||
baseorm.BaseOrm
|
||||
DelayType string `json:"delay_type"` //操作类型
|
||||
DelayTile string `json:"delay_title"` //操作标题
|
||||
DelayContent string `json:"delay_content"` //操作内容
|
||||
DelayType string `gorm:"size:50" json:"delay_type"` //操作类型
|
||||
DelayTile string `gorm:"size:255" json:"delay_title"` //操作标题
|
||||
DelayContent string `gorm:"type:text" json:"delay_content"` //操作内容
|
||||
}
|
||||
|
||||
@@ -7,13 +7,13 @@ import (
|
||||
// FirewallIPBlock 防火墙IP封禁记录表(操作系统级别的防火墙封禁)
|
||||
type FirewallIPBlock struct {
|
||||
baseorm.BaseOrm
|
||||
HostCode string `json:"host_code"` // 网站唯一码(主要键)
|
||||
IP string `json:"ip"` // 被封禁的IP地址,支持单个IP或CIDR格式
|
||||
Reason string `json:"reason"` // 封禁原因
|
||||
BlockType string `json:"block_type"` // 封禁类型:manual-手动封禁, auto-自动封禁, temp-临时封禁
|
||||
Status string `json:"status"` // 状态:active-已生效, inactive-已失效, pending-待生效
|
||||
ExpireTime int64 `json:"expire_time"` // 过期时间(时间戳,0表示永久)
|
||||
Remarks string `json:"remarks"` // 备注
|
||||
HostCode string `gorm:"size:64" json:"host_code"` // 网站唯一码(主要键)
|
||||
IP string `gorm:"size:64" json:"ip"` // 被封禁的IP地址,支持单个IP或CIDR格式
|
||||
Reason string `gorm:"type:text" json:"reason"` // 封禁原因
|
||||
BlockType string `gorm:"size:32" json:"block_type"` // 封禁类型:manual-手动封禁, auto-自动封禁, temp-临时封禁
|
||||
Status string `gorm:"size:16" json:"status"` // 状态:active-已生效, inactive-已失效, pending-待生效
|
||||
ExpireTime int64 `json:"expire_time"` // 过期时间(时间戳,0表示永久)
|
||||
Remarks string `gorm:"size:500" json:"remarks"` // 备注
|
||||
}
|
||||
|
||||
// TableName 表名
|
||||
|
||||
+42
-42
@@ -7,48 +7,48 @@ import (
|
||||
|
||||
type Hosts struct {
|
||||
baseorm.BaseOrm
|
||||
Code string `json:"code"` //唯一码
|
||||
Host string `json:"host"` //域名
|
||||
Port int `json:"port"` //端口
|
||||
Ssl int `json:"ssl"` //是否是ssl
|
||||
GUARD_STATUS int `json:"guard_status"` //防御状态 1 是开启防御 0 是防御关闭
|
||||
REMOTE_SYSTEM string `json:"remote_system"` //是宝塔 phpstudy等
|
||||
REMOTE_APP string `json:"remote_app"` //是什么类型的应用
|
||||
Remote_host string `json:"remote_host"` //远端域名
|
||||
Remote_port int `json:"remote_port"` //远端端口
|
||||
Remote_ip string `json:"remote_ip"` //远端指定IP
|
||||
Certfile string `json:"certfile"` //证书文件
|
||||
Keyfile string `json:"keyfile"` //密钥文件
|
||||
REMARKS string `json:"remarks"` //备注
|
||||
GLOBAL_HOST int `json:"global_host"` //默认全局 1 全局 0非全局
|
||||
DEFENSE_JSON string `json:"defense_json"` //自身防御 json
|
||||
START_STATUS int `json:"start_status"` //启动状态 如果是0 启动 ; 如果是1 不启动
|
||||
EXCLUDE_URL_LOG string `json:"exclude_url_log"` //排除的url开头的数据 换行隔开
|
||||
IsEnableLoadBalance int `json:"is_enable_load_balance"` //是否激活负载 1 激活 非1 没有激活
|
||||
LoadBalanceStage int `json:"load_balance_stage"` //负载策略
|
||||
UnrestrictedPort int `json:"unrestricted_port"` //不限来源匹配端口 0 限制 1,不限制
|
||||
BindSslId string `json:"bind_ssl_id"` //绑定SSL的ID
|
||||
AutoJumpHTTPS int `json:"auto_jump_https"` //是否自动跳转https 0 不自动 1 强制80跳转https
|
||||
BindMoreHost string `json:"bind_more_host"` //绑定多域名
|
||||
IsTransBackDomain int `json:"is_trans_back_domain"` //是否传递后端域名到后端服务器侧
|
||||
BindMorePort string `json:"bind_more_port"` //是否绑定多个端口
|
||||
IsEnableHttpAuthBase int `json:"is_enable_http_auth_base"` //是否 HTTPAuthBase 1 激活 非1 没有激活
|
||||
HttpAuthBaseType string `json:"http_auth_base_type"` //认证类型 authorization(默认Basic Auth) custom(自定义页面)
|
||||
HttpAuthPathPrefix string `json:"http_auth_path_prefix"` //HTTP认证路径前缀,用于隐藏系统特征,默认为随机生成
|
||||
ResponseTimeOut int `json:"response_time_out"` //响应超时时间 默认60秒,为0则无限等待
|
||||
HealthyJSON string `json:"healthy_json"` //后端健康度检测 json
|
||||
InsecureSkipVerify int `json:"insecure_skip_verify"` //是否开启后端https证书有效性验证 默认 0 是校验 1 是不校验
|
||||
CaptchaJSON string `json:"captcha_json"` //验证码配置 json
|
||||
AntiLeechJSON string `json:"anti_leech_json"` //防盗链配置 json
|
||||
CacheJSON string `json:"cache_json"` //缓存配置 json
|
||||
StaticSiteJSON string `json:"static_site_json"` //静态站点配置 json
|
||||
TransportJSON string `json:"transport_json"` //传输配置 json
|
||||
DefaultEncoding string `json:"default_encoding"` //默认编码 utf-8 或者 gbk auto字符串自动选择
|
||||
LogOnlyMode int `json:"log_only_mode"` //仅记录模式 1开启 0关闭
|
||||
CustomHeadersJSON string `json:"custom_headers_json"` //自定义头信息配置 json
|
||||
CustomResponseHeadersJSON string `json:"custom_response_headers_json"` //自定义响应头信息配置 json
|
||||
ResponseCompressJSON string `json:"response_compress_json"` //响应压缩配置 json(Gzip/Brotli/Zstd)
|
||||
IPMode string `json:"ip_mode"` //IP提取模式: "nic" 网卡模式 或 "proxy" 代理模式
|
||||
Code string `gorm:"size:64" json:"code"` //唯一码
|
||||
Host string `gorm:"size:255" json:"host"` //域名
|
||||
Port int `json:"port"` //端口
|
||||
Ssl int `json:"ssl"` //是否是ssl
|
||||
GUARD_STATUS int `json:"guard_status"` //防御状态 1 是开启防御 0 是防御关闭
|
||||
REMOTE_SYSTEM string `gorm:"size:50" json:"remote_system"` //是宝塔 phpstudy等
|
||||
REMOTE_APP string `gorm:"size:100" json:"remote_app"` //是什么类型的应用
|
||||
Remote_host string `gorm:"size:255" json:"remote_host"` //远端域名
|
||||
Remote_port int `json:"remote_port"` //远端端口
|
||||
Remote_ip string `gorm:"size:64" json:"remote_ip"` //远端指定IP
|
||||
Certfile string `gorm:"type:text" json:"certfile"` //证书文件
|
||||
Keyfile string `gorm:"type:text" json:"keyfile"` //密钥文件
|
||||
REMARKS string `gorm:"size:500" json:"remarks"` //备注
|
||||
GLOBAL_HOST int `json:"global_host"` //默认全局 1 全局 0非全局
|
||||
DEFENSE_JSON string `gorm:"type:text" json:"defense_json"` //自身防御 json
|
||||
START_STATUS int `json:"start_status"` //启动状态 如果是0 启动 ; 如果是1 不启动
|
||||
EXCLUDE_URL_LOG string `gorm:"type:text" json:"exclude_url_log"` //排除的url开头的数据 换行隔开
|
||||
IsEnableLoadBalance int `json:"is_enable_load_balance"` //是否激活负载 1 激活 非1 没有激活
|
||||
LoadBalanceStage int `json:"load_balance_stage"` //负载策略
|
||||
UnrestrictedPort int `json:"unrestricted_port"` //不限来源匹配端口 0 限制 1,不限制
|
||||
BindSslId string `gorm:"size:64" json:"bind_ssl_id"` //绑定SSL的ID
|
||||
AutoJumpHTTPS int `json:"auto_jump_https"` //是否自动跳转https 0 不自动 1 强制80跳转https
|
||||
BindMoreHost string `gorm:"type:text" json:"bind_more_host"` //绑定多域名
|
||||
IsTransBackDomain int `json:"is_trans_back_domain"` //是否传递后端域名到后端服务器侧
|
||||
BindMorePort string `gorm:"size:255" json:"bind_more_port"` //是否绑定多个端口
|
||||
IsEnableHttpAuthBase int `json:"is_enable_http_auth_base"` //是否 HTTPAuthBase 1 激活 非1 没有激活
|
||||
HttpAuthBaseType string `gorm:"size:50" json:"http_auth_base_type"` //认证类型 authorization(默认Basic Auth) custom(自定义页面)
|
||||
HttpAuthPathPrefix string `gorm:"size:255" json:"http_auth_path_prefix"` //HTTP认证路径前缀,用于隐藏系统特征,默认为随机生成
|
||||
ResponseTimeOut int `json:"response_time_out"` //响应超时时间 默认60秒,为0则无限等待
|
||||
HealthyJSON string `gorm:"type:text" json:"healthy_json"` //后端健康度检测 json
|
||||
InsecureSkipVerify int `json:"insecure_skip_verify"` //是否开启后端https证书有效性验证 默认 0 是校验 1 是不校验
|
||||
CaptchaJSON string `gorm:"type:text" json:"captcha_json"` //验证码配置 json
|
||||
AntiLeechJSON string `gorm:"type:text" json:"anti_leech_json"` //防盗链配置 json
|
||||
CacheJSON string `gorm:"type:text" json:"cache_json"` //缓存配置 json
|
||||
StaticSiteJSON string `gorm:"type:text" json:"static_site_json"` //静态站点配置 json
|
||||
TransportJSON string `gorm:"type:text" json:"transport_json"` //传输配置 json
|
||||
DefaultEncoding string `gorm:"size:20" json:"default_encoding"` //默认编码 utf-8 或者 gbk auto字符串自动选择
|
||||
LogOnlyMode int `json:"log_only_mode"` //仅记录模式 1开启 0关闭
|
||||
CustomHeadersJSON string `gorm:"type:text" json:"custom_headers_json"` //自定义头信息配置 json
|
||||
CustomResponseHeadersJSON string `gorm:"type:text" json:"custom_response_headers_json"` //自定义响应头信息配置 json
|
||||
ResponseCompressJSON string `gorm:"type:text" json:"response_compress_json"` //响应压缩配置 json(Gzip/Brotli/Zstd)
|
||||
IPMode string `gorm:"size:20" json:"ip_mode"` //IP提取模式: "nic" 网卡模式 或 "proxy" 代理模式
|
||||
}
|
||||
|
||||
type HostsDefense struct {
|
||||
|
||||
@@ -4,7 +4,7 @@ import "SamWaf/model/baseorm"
|
||||
|
||||
type HttpAuthBase struct {
|
||||
baseorm.BaseOrm
|
||||
HostCode string `json:"host_code"` //网站唯一码(主要键)
|
||||
UserName string `json:"user_name"` // 用户名
|
||||
Password string `json:"password"` // 访问密码
|
||||
HostCode string `gorm:"size:64" json:"host_code"` //网站唯一码(主要键)
|
||||
UserName string `gorm:"size:255" json:"user_name"` // 用户名
|
||||
Password string `gorm:"size:255" json:"password"` // 访问密码
|
||||
}
|
||||
|
||||
+4
-4
@@ -9,9 +9,9 @@ import (
|
||||
*/
|
||||
type LDPUrl struct {
|
||||
baseorm.BaseOrm
|
||||
HostCode string `json:"host_code"` //网站唯一码(主要键)
|
||||
HostCode string `gorm:"size:64" json:"host_code"` //网站唯一码(主要键)
|
||||
//CompareCol string `json:"CompareCol"` //判断字段
|
||||
CompareType string `json:"compare_type"` //判断类型,包含、开始、结束、完全匹配
|
||||
Url string `json:"url"` //请求地址
|
||||
Remarks string `json:"remarks"` //备注
|
||||
CompareType string `gorm:"size:50" json:"compare_type"` //判断类型,包含、开始、结束、完全匹配
|
||||
Url string `gorm:"type:text" json:"url"` //请求地址
|
||||
Remarks string `gorm:"size:500" json:"remarks"` //备注
|
||||
}
|
||||
|
||||
@@ -7,9 +7,9 @@ import "SamWaf/model/baseorm"
|
||||
*/
|
||||
type LoadBalance struct {
|
||||
baseorm.BaseOrm
|
||||
HostCode string `json:"host_code"` //网站唯一码(主要键)
|
||||
Remote_port int `json:"remote_port"` //远端端口
|
||||
Remote_ip string `json:"remote_ip"` //远端指定IP
|
||||
Weight int `json:"weight"` //权重
|
||||
Remarks string `json:"remarks"` //备注
|
||||
HostCode string `gorm:"size:64" json:"host_code"` //网站唯一码(主要键)
|
||||
Remote_port int `json:"remote_port"` //远端端口
|
||||
Remote_ip string `gorm:"size:64" json:"remote_ip"` //远端指定IP
|
||||
Weight int `json:"weight"` //权重
|
||||
Remarks string `gorm:"size:500" json:"remarks"` //备注
|
||||
}
|
||||
|
||||
@@ -10,12 +10,12 @@ import (
|
||||
*/
|
||||
type NotifyChannel struct {
|
||||
baseorm.BaseOrm
|
||||
Name string `json:"name"` // 渠道名称
|
||||
Type string `json:"type"` // 渠道类型:dingtalk, feishu, wechat, email等
|
||||
WebhookURL string `json:"webhook_url"` // Webhook地址
|
||||
Secret string `json:"secret"` // 密钥(用于签名验证)
|
||||
AccessToken string `json:"access_token"` // 访问令牌
|
||||
ConfigJSON string `json:"config_json"` // 额外配置(JSON格式)
|
||||
Status int `json:"status"` // 状态:1启用,0禁用
|
||||
Remarks string `json:"remarks"` // 备注
|
||||
Name string `gorm:"size:255" json:"name"` // 渠道名称
|
||||
Type string `gorm:"size:50" json:"type"` // 渠道类型:dingtalk, feishu, wechat, email等
|
||||
WebhookURL string `gorm:"size:500" json:"webhook_url"` // Webhook地址
|
||||
Secret string `gorm:"size:255" json:"secret"` // 密钥(用于签名验证)
|
||||
AccessToken string `gorm:"size:500" json:"access_token"` // 访问令牌
|
||||
ConfigJSON string `gorm:"type:text" json:"config_json"` // 额外配置(JSON格式)
|
||||
Status int `json:"status"` // 状态:1启用,0禁用
|
||||
Remarks string `gorm:"size:500" json:"remarks"` // 备注
|
||||
}
|
||||
|
||||
+10
-10
@@ -10,14 +10,14 @@ import (
|
||||
*/
|
||||
type NotifyLog struct {
|
||||
baseorm.BaseOrm
|
||||
ChannelId string `json:"channel_id"` // 渠道ID
|
||||
ChannelName string `json:"channel_name"` // 渠道名称
|
||||
ChannelType string `json:"channel_type"` // 渠道类型
|
||||
MessageType string `json:"message_type"` // 消息类型
|
||||
MessageTitle string `json:"message_title"` // 消息标题
|
||||
MessageContent string `json:"message_content"` // 消息内容
|
||||
Recipients string `json:"recipients"` // 收件人(仅邮件类型)
|
||||
Status int `json:"status"` // 发送状态:1成功,0失败
|
||||
ErrorMsg string `json:"error_msg"` // 错误信息
|
||||
SendTime string `json:"send_time"` // 发送时间
|
||||
ChannelId string `gorm:"size:64" json:"channel_id"` // 渠道ID
|
||||
ChannelName string `gorm:"size:255" json:"channel_name"` // 渠道名称
|
||||
ChannelType string `gorm:"size:50" json:"channel_type"` // 渠道类型
|
||||
MessageType string `gorm:"size:100" json:"message_type"` // 消息类型
|
||||
MessageTitle string `gorm:"size:500" json:"message_title"` // 消息标题
|
||||
MessageContent string `gorm:"type:text" json:"message_content"` // 消息内容
|
||||
Recipients string `gorm:"type:text" json:"recipients"` // 收件人(仅邮件类型)
|
||||
Status int `json:"status"` // 发送状态:1成功,0失败
|
||||
ErrorMsg string `gorm:"type:text" json:"error_msg"` // 错误信息
|
||||
SendTime string `gorm:"size:100" json:"send_time"` // 发送时间
|
||||
}
|
||||
|
||||
@@ -10,12 +10,12 @@ import (
|
||||
*/
|
||||
type NotifySubscription struct {
|
||||
baseorm.BaseOrm
|
||||
ChannelId string `json:"channel_id"` // 关联的渠道ID
|
||||
MessageType string `json:"message_type"` // 消息类型:user_login, attack_info, weekly_report等
|
||||
Recipients string `json:"recipients"` // 收件人列表(逗号分隔,主要用于邮件类型)留空则使用渠道默认收件人
|
||||
Status int `json:"status"` // 状态:1启用,0禁用
|
||||
FilterJSON string `json:"filter_json"` // 过滤条件(JSON格式)
|
||||
Remarks string `json:"remarks"` // 备注
|
||||
ChannelId string `gorm:"size:64" json:"channel_id"` // 关联的渠道ID
|
||||
MessageType string `gorm:"size:100" json:"message_type"` // 消息类型:user_login, attack_info, weekly_report等
|
||||
Recipients string `gorm:"type:text" json:"recipients"` // 收件人列表(逗号分隔,主要用于邮件类型)留空则使用渠道默认收件人
|
||||
Status int `json:"status"` // 状态:1启用,0禁用
|
||||
FilterJSON string `gorm:"type:text" json:"filter_json"` // 过滤条件(JSON格式)
|
||||
Remarks string `gorm:"size:500" json:"remarks"` // 备注
|
||||
}
|
||||
|
||||
// 消息类型常量
|
||||
|
||||
+6
-6
@@ -8,10 +8,10 @@ import "SamWaf/model/baseorm"
|
||||
*/
|
||||
type OneKeyMod struct {
|
||||
baseorm.BaseOrm
|
||||
OpSystem string `json:"op_system"` //系统类型
|
||||
FilePath string `json:"file_path"` //文件路径
|
||||
BeforeContent string `json:"before_content"` //修改前内容
|
||||
AfterContent string `json:"after_content"` //修改后内容
|
||||
IsRestore int `json:"is_restore"` //是否还原
|
||||
Remarks string `json:"remarks"` //备注
|
||||
OpSystem string `gorm:"size:50" json:"op_system"` //系统类型
|
||||
FilePath string `gorm:"size:500" json:"file_path"` //文件路径
|
||||
BeforeContent string `gorm:"type:text" json:"before_content"` //修改前内容
|
||||
AfterContent string `gorm:"type:text" json:"after_content"` //修改后内容
|
||||
IsRestore int `json:"is_restore"` //是否还原
|
||||
Remarks string `gorm:"size:500" json:"remarks"` //备注
|
||||
}
|
||||
|
||||
@@ -10,13 +10,13 @@ import (
|
||||
*/
|
||||
type OPlatformKey struct {
|
||||
baseorm.BaseOrm
|
||||
KeyName string `json:"key_name"` //Key 名称
|
||||
ApiKey string `json:"api_key"` //API Key(自动生成,用于 X-API-Key 请求头鉴权)
|
||||
Status int `json:"status"` //状态 1启用 0禁用
|
||||
Remark string `json:"remark"` //备注
|
||||
RateLimit int64 `json:"rate_limit"` //每分钟限流次数,0不限
|
||||
IPWhitelist string `json:"ip_whitelist"` //IP白名单,逗号分隔,空不限
|
||||
ExpireTime string `json:"expire_time"` //过期时间,空不过期
|
||||
LastUseTime string `json:"last_use_time"` //最后使用时间
|
||||
CallCount int64 `json:"call_count"` //累计调用次数
|
||||
KeyName string `gorm:"size:255" json:"key_name"` //Key 名称
|
||||
ApiKey string `gorm:"size:255" json:"api_key"` //API Key(自动生成,用于 X-API-Key 请求头鉴权)
|
||||
Status int `json:"status"` //状态 1启用 0禁用
|
||||
Remark string `gorm:"size:500" json:"remark"` //备注
|
||||
RateLimit int64 `json:"rate_limit"` //每分钟限流次数,0不限
|
||||
IPWhitelist string `gorm:"type:text" json:"ip_whitelist"` //IP白名单,逗号分隔,空不限
|
||||
ExpireTime string `gorm:"size:50" json:"expire_time"` //过期时间,空不过期
|
||||
LastUseTime string `gorm:"size:50" json:"last_use_time"` //最后使用时间
|
||||
CallCount int64 `json:"call_count"` //累计调用次数
|
||||
}
|
||||
|
||||
+10
-10
@@ -10,14 +10,14 @@ import (
|
||||
*/
|
||||
type OPlatformLog struct {
|
||||
baseorm.BaseOrm
|
||||
ApiKeyId string `json:"api_key_id"` //关联的Key ID
|
||||
KeyName string `json:"key_name"` //Key名称(冗余,方便查询)
|
||||
RequestPath string `json:"request_path"` //请求路径
|
||||
RequestMethod string `json:"request_method"` //请求方法
|
||||
RequestBody string `json:"request_body"` //请求体(非文件类型记录)
|
||||
ResponseBody string `json:"response_body"` //响应体
|
||||
ClientIP string `json:"client_ip"` //调用者IP
|
||||
StatusCode int `json:"status_code"` //响应状态码
|
||||
Duration int64 `json:"duration"` //耗时(ms)
|
||||
TimeStr string `json:"time_str"` //调用时间字符串
|
||||
ApiKeyId string `gorm:"size:64" json:"api_key_id"` //关联的Key ID
|
||||
KeyName string `gorm:"size:255" json:"key_name"` //Key名称(冗余,方便查询)
|
||||
RequestPath string `gorm:"size:500" json:"request_path"` //请求路径
|
||||
RequestMethod string `gorm:"size:20" json:"request_method"` //请求方法
|
||||
RequestBody string `gorm:"type:text" json:"request_body"` //请求体(非文件类型记录)
|
||||
ResponseBody string `gorm:"type:text" json:"response_body"` //响应体
|
||||
ClientIP string `gorm:"size:64" json:"client_ip"` //调用者IP
|
||||
StatusCode int `json:"status_code"` //响应状态码
|
||||
Duration int64 `json:"duration"` //耗时(ms)
|
||||
TimeStr string `gorm:"size:100" json:"time_str"` //调用时间字符串
|
||||
}
|
||||
|
||||
+5
-5
@@ -4,9 +4,9 @@ import "SamWaf/model/baseorm"
|
||||
|
||||
type Otp struct {
|
||||
baseorm.BaseOrm
|
||||
UserName string `json:"user_name"` //用户名
|
||||
Url string `json:"url"` //URL
|
||||
Secret string `json:"secret"` //密钥
|
||||
Issuer string `json:"issuer"` //发行者标识
|
||||
Remarks string `json:"remarks"` //备注
|
||||
UserName string `gorm:"size:255" json:"user_name"` //用户名
|
||||
Url string `gorm:"size:500" json:"url"` //URL
|
||||
Secret string `gorm:"size:255" json:"secret"` //密钥
|
||||
Issuer string `gorm:"size:255" json:"issuer"` //发行者标识
|
||||
Remarks string `gorm:"size:500" json:"remarks"` //备注
|
||||
}
|
||||
|
||||
@@ -4,6 +4,6 @@ import "SamWaf/model/baseorm"
|
||||
|
||||
type PrivateGroup struct {
|
||||
baseorm.BaseOrm
|
||||
PrivateGroupName string `json:"private_group_name"` //分组名称
|
||||
PrivateGroupBelongCloud string `json:"private_group_belong_cloud"` //分组归属云
|
||||
PrivateGroupName string `gorm:"size:255" json:"private_group_name"` //分组名称
|
||||
PrivateGroupBelongCloud string `gorm:"size:100" json:"private_group_belong_cloud"` //分组归属云
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@ import "SamWaf/model/baseorm"
|
||||
|
||||
type PrivateInfo struct {
|
||||
baseorm.BaseOrm
|
||||
PrivateKey string `json:"private_key"` //密钥key
|
||||
PrivateValue string `json:"private_value"` //密钥值
|
||||
PrivateGroupName string `json:"private_group_name"` //分组名称
|
||||
PrivateGroupBelongCloud string `json:"private_group_belong_cloud"` //分组归属云
|
||||
Remarks string `json:"remarks"` //备注
|
||||
PrivateKey string `gorm:"size:255" json:"private_key"` //密钥key
|
||||
PrivateValue string `gorm:"type:text" json:"private_value"` //密钥值
|
||||
PrivateGroupName string `gorm:"size:255" json:"private_group_name"` //分组名称
|
||||
PrivateGroupBelongCloud string `gorm:"size:100" json:"private_group_belong_cloud"` //分组归属云
|
||||
Remarks string `gorm:"size:500" json:"remarks"` //备注
|
||||
}
|
||||
|
||||
+10
-10
@@ -6,14 +6,14 @@ import (
|
||||
|
||||
type Rules struct {
|
||||
baseorm.BaseOrm
|
||||
HostCode string `json:"host_code"` //主机唯一码
|
||||
RuleCode string `json:"rule_code"` //规则的唯一码
|
||||
RuleName string `json:"rule_name"` //规则名称
|
||||
RuleContent string `json:"rule_content"` //规则内容
|
||||
RuleContentJSON string `json:"rule_content_json"` //规则JSON内容
|
||||
RuleVersionName string `json:"rule_version_name"` //规则版本名
|
||||
RuleVersion int `json:"rule_version"` //规则版本号
|
||||
IsPublicRule int `json:"is_public_rule"` //是否为公共规则
|
||||
IsManualRule int `json:"is_manual_rule"` //是否为手工写规则 1:手工编写 0 :UI界面形式
|
||||
RuleStatus int `json:"rule_status"` //规则是否开启 1,开启 0,关闭不生效 999 删除
|
||||
HostCode string `gorm:"size:64" json:"host_code"` //主机唯一码
|
||||
RuleCode string `gorm:"size:64" json:"rule_code"` //规则的唯一码
|
||||
RuleName string `gorm:"size:255" json:"rule_name"` //规则名称
|
||||
RuleContent string `gorm:"type:text" json:"rule_content"` //规则内容
|
||||
RuleContentJSON string `gorm:"type:text" json:"rule_content_json"` //规则JSON内容
|
||||
RuleVersionName string `gorm:"size:100" json:"rule_version_name"` //规则版本名
|
||||
RuleVersion int `json:"rule_version"` //规则版本号
|
||||
IsPublicRule int `json:"is_public_rule"` //是否为公共规则
|
||||
IsManualRule int `json:"is_manual_rule"` //是否为手工写规则 1:手工编写 0 :UI界面形式
|
||||
RuleStatus int `json:"rule_status"` //规则是否开启 1,开启 0,关闭不生效 999 删除
|
||||
}
|
||||
|
||||
+4
-4
@@ -4,8 +4,8 @@ import "SamWaf/model/baseorm"
|
||||
|
||||
type Sensitive struct {
|
||||
baseorm.BaseOrm
|
||||
CheckDirection string `json:"check_direction"` //敏感词检测方向 in ,out ,all
|
||||
Action string `json:"action"` //敏感词检测后动作 deny,replace
|
||||
Content string `json:"content"` //内容
|
||||
Remarks string `json:"remarks"` //备注
|
||||
CheckDirection string `gorm:"size:50" json:"check_direction"` //敏感词检测方向 in ,out ,all
|
||||
Action string `gorm:"size:50" json:"action"` //敏感词检测后动作 deny,replace
|
||||
Content string `gorm:"type:text" json:"content"` //内容
|
||||
Remarks string `gorm:"size:500" json:"remarks"` //备注
|
||||
}
|
||||
|
||||
+5
-5
@@ -12,11 +12,11 @@ import (
|
||||
*/
|
||||
type ShareDb struct {
|
||||
baseorm.BaseOrm
|
||||
DbLogicType string `json:"db_logic_type"` //数据库逻辑类型 默认:"log"
|
||||
StartTime customtype.JsonTime `json:"start_time"` //开始时间
|
||||
EndTime customtype.JsonTime `json:"end_time"` //结束时间
|
||||
FileName string `json:"file_name"` //文件名:SQLite 下为 .db 文件名;MySQL 下为表名(无后缀)
|
||||
Cnt int64 `json:"cnt"` //当前数量
|
||||
DbLogicType string `gorm:"size:50" json:"db_logic_type"` //数据库逻辑类型 默认:"log"
|
||||
StartTime customtype.JsonTime `json:"start_time"` //开始时间
|
||||
EndTime customtype.JsonTime `json:"end_time"` //结束时间
|
||||
FileName string `gorm:"size:255" json:"file_name"` //文件名:SQLite 下为 .db 文件名;MySQL 下为表名(无后缀)
|
||||
Cnt int64 `json:"cnt"` //当前数量
|
||||
}
|
||||
|
||||
// IsTableShard 在 MySQL/SQL Server 模式下返回 true(FileName 存的是表名而非文件名)
|
||||
|
||||
+10
-10
@@ -13,16 +13,16 @@ import (
|
||||
|
||||
type SslConfig struct {
|
||||
baseorm.BaseOrm
|
||||
CertContent string `json:"cert_content"` // 证书文件内容
|
||||
KeyContent string `json:"key_content"` // 密钥文件内容
|
||||
SerialNo string `json:"serial_no"` // 证书序列号
|
||||
Subject string `json:"subject"` // 证书主题
|
||||
Issuer string `json:"issuer"` // 颁发者
|
||||
ValidFrom time.Time `json:"valid_from"` // 证书有效期开始时间
|
||||
ValidTo time.Time `json:"valid_to"` // 证书有效期结束时间
|
||||
Domains string `json:"domains"` // 证书适用的域名
|
||||
KeyPath string `json:"key_path"` //密钥文件位置
|
||||
CertPath string `json:"cert_path"` //crt文件配置
|
||||
CertContent string `gorm:"type:text" json:"cert_content"` // 证书文件内容
|
||||
KeyContent string `gorm:"type:text" json:"key_content"` // 密钥文件内容
|
||||
SerialNo string `gorm:"size:255" json:"serial_no"` // 证书序列号
|
||||
Subject string `gorm:"size:500" json:"subject"` // 证书主题
|
||||
Issuer string `gorm:"size:500" json:"issuer"` // 颁发者
|
||||
ValidFrom time.Time `json:"valid_from"` // 证书有效期开始时间
|
||||
ValidTo time.Time `json:"valid_to"` // 证书有效期结束时间
|
||||
Domains string `gorm:"type:text" json:"domains"` // 证书适用的域名
|
||||
KeyPath string `gorm:"size:500" json:"key_path"` //密钥文件位置
|
||||
CertPath string `gorm:"size:500" json:"cert_path"` //crt文件配置
|
||||
}
|
||||
|
||||
// ExpirationMessage 获取到期提示信息
|
||||
|
||||
+6
-6
@@ -8,12 +8,12 @@ import (
|
||||
|
||||
type SslExpire struct {
|
||||
baseorm.BaseOrm
|
||||
Domain string `json:"domain"` // 证书适用的域名
|
||||
Port int `json:"port"` // 端口
|
||||
ValidTo time.Time `json:"valid_to"` // 证书有效期结束时间
|
||||
VisitLog string `json:"visit_log"` //访问日志
|
||||
LastDetect time.Time `json:"last_detect"` // 最后检测时间
|
||||
Status string `json:"status"` //状态
|
||||
Domain string `gorm:"size:255" json:"domain"` // 证书适用的域名
|
||||
Port int `json:"port"` // 端口
|
||||
ValidTo time.Time `json:"valid_to"` // 证书有效期结束时间
|
||||
VisitLog string `gorm:"size:500" json:"visit_log"` //访问日志
|
||||
LastDetect time.Time `json:"last_detect"` // 最后检测时间
|
||||
Status string `gorm:"size:50" json:"status"` //状态
|
||||
}
|
||||
|
||||
// ExpirationMessage 获取到期提示信息
|
||||
|
||||
+20
-20
@@ -10,26 +10,26 @@ import (
|
||||
// SslOrder 证书申请
|
||||
type SslOrder struct {
|
||||
baseorm.BaseOrm
|
||||
HostCode string `json:"host_code"` //网站唯一码(主要键)
|
||||
ApplyPlatform string `json:"apply_platform"` //申请平台
|
||||
ApplyMethod string `json:"apply_method"` //申请方式http01,dns
|
||||
SkipDNSVerify int64 `json:"skip_dns_verify"` //dns01是否跳过本地DNS传播校验 0否 1是
|
||||
ApplyDns string `json:"apply_dns"` //申请dns服务商
|
||||
ApplyEmail string `json:"apply_email"` //申请邮箱
|
||||
ApplyKey string `json:"apply_key"` //申请私钥
|
||||
ApplyDomain string `json:"apply_domain"` //申请域名
|
||||
ApplyStatus string `json:"apply_status"` //申请状态 提交,完成,已续签
|
||||
ResultError string `json:"result_error"` //申请错误的详情
|
||||
ResultDomain string `json:"result_domain"` //返回结果信息
|
||||
ResultCertURL string `json:"result_cert_url"` //证书连接位置
|
||||
ResultCertStableURL string `json:"result_cert_stable_url"` //证书固定连接位置
|
||||
ResultPrivateKey []byte `json:"result_private_key"` //证书私钥信息
|
||||
ResultCertificate []byte `json:"result_certificate"` //证书信息
|
||||
ResultIssuerCertificate []byte `json:"result_issuer_certificate"` //证书所属信息
|
||||
ResultValidTo time.Time `json:"result_valid_to"` //证书有效期结束时间
|
||||
ResultCSR []byte `json:"result_csr"` //csr信息
|
||||
Remarks string `json:"remarks"` //备注信息
|
||||
PrivateGroupName string `json:"private_group_name"` //密钥分组名称DNS有效
|
||||
HostCode string `gorm:"size:64" json:"host_code"` //网站唯一码(主要键)
|
||||
ApplyPlatform string `gorm:"size:100" json:"apply_platform"` //申请平台
|
||||
ApplyMethod string `gorm:"size:50" json:"apply_method"` //申请方式http01,dns
|
||||
SkipDNSVerify int64 `json:"skip_dns_verify"` //dns01是否跳过本地DNS传播校验 0否 1是
|
||||
ApplyDns string `gorm:"size:100" json:"apply_dns"` //申请dns服务商
|
||||
ApplyEmail string `gorm:"size:255" json:"apply_email"` //申请邮箱
|
||||
ApplyKey string `gorm:"size:255" json:"apply_key"` //申请私钥
|
||||
ApplyDomain string `gorm:"size:255" json:"apply_domain"` //申请域名
|
||||
ApplyStatus string `gorm:"size:50" json:"apply_status"` //申请状态 提交,完成,已续签
|
||||
ResultError string `gorm:"type:text" json:"result_error"` //申请错误的详情
|
||||
ResultDomain string `gorm:"size:255" json:"result_domain"` //返回结果信息
|
||||
ResultCertURL string `gorm:"size:500" json:"result_cert_url"` //证书连接位置
|
||||
ResultCertStableURL string `gorm:"size:500" json:"result_cert_stable_url"` //证书固定连接位置
|
||||
ResultPrivateKey []byte `json:"result_private_key"` //证书私钥信息
|
||||
ResultCertificate []byte `json:"result_certificate"` //证书信息
|
||||
ResultIssuerCertificate []byte `json:"result_issuer_certificate"` //证书所属信息
|
||||
ResultValidTo time.Time `json:"result_valid_to"` //证书有效期结束时间
|
||||
ResultCSR []byte `json:"result_csr"` //csr信息
|
||||
Remarks string `gorm:"size:500" json:"remarks"` //备注信息
|
||||
PrivateGroupName string `gorm:"size:255" json:"private_group_name"` //密钥分组名称DNS有效
|
||||
}
|
||||
|
||||
// ExpirationMessage 获取到期提示信息
|
||||
|
||||
+37
-37
@@ -22,11 +22,11 @@ type StatsTotal struct {
|
||||
*/
|
||||
type StatsDay struct {
|
||||
baseorm.BaseOrm
|
||||
HostCode string `json:"host_code"` //网站唯一码(主要键)
|
||||
Day int `json:"day"` //年月日(主要键)
|
||||
Host string `json:"host"` //域名
|
||||
Type string `json:"type"` //类型 放行,阻止
|
||||
Count int `json:"count"` //数量
|
||||
HostCode string `gorm:"size:64" json:"host_code"` //网站唯一码(主要键)
|
||||
Day int `json:"day"` //年月日(主要键)
|
||||
Host string `gorm:"size:255" json:"host"` //域名
|
||||
Type string `gorm:"size:32" json:"type"` //类型 放行,阻止
|
||||
Count int `json:"count"` //数量
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -35,12 +35,12 @@ type StatsDay struct {
|
||||
*/
|
||||
type StatsIPDay struct {
|
||||
baseorm.BaseOrm
|
||||
HostCode string `json:"host_code"` //网站唯一码(主要键)
|
||||
Day int `json:"day"` //年月日(主要键)
|
||||
Host string `json:"host"` //域名
|
||||
IP string `json:"ip"` //ip
|
||||
Type string `json:"type"` //类型 放行,阻止
|
||||
Count int `json:"count"` //数量
|
||||
HostCode string `gorm:"size:64" json:"host_code"` //网站唯一码(主要键)
|
||||
Day int `json:"day"` //年月日(主要键)
|
||||
Host string `gorm:"size:255" json:"host"` //域名
|
||||
IP string `gorm:"size:64" json:"ip"` //ip
|
||||
Type string `gorm:"size:32" json:"type"` //类型 放行,阻止
|
||||
Count int `json:"count"` //数量
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -49,14 +49,14 @@ type StatsIPDay struct {
|
||||
*/
|
||||
type StatsIPCityDay struct {
|
||||
baseorm.BaseOrm
|
||||
HostCode string `json:"host_code"` //网站唯一码(主要键)
|
||||
Day int `json:"day"` //年月日(主要键)
|
||||
Host string `json:"host"` //域名
|
||||
Country string `json:"country"` //国家
|
||||
Province string `json:"province"` //省份
|
||||
City string `json:"city"` //城市
|
||||
Type string `json:"type"` //类型 放行,阻止
|
||||
Count int `json:"count"` //数量
|
||||
HostCode string `gorm:"size:64" json:"host_code"` //网站唯一码(主要键)
|
||||
Day int `json:"day"` //年月日(主要键)
|
||||
Host string `gorm:"size:255" json:"host"` //域名
|
||||
Country string `gorm:"size:100" json:"country"` //国家
|
||||
Province string `gorm:"size:100" json:"province"` //省份
|
||||
City string `gorm:"size:100" json:"city"` //城市
|
||||
Type string `gorm:"size:32" json:"type"` //类型 放行,阻止
|
||||
Count int `json:"count"` //数量
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -95,15 +95,15 @@ type StatsIPCountMore struct {
|
||||
*/
|
||||
type StatsSiteDay struct {
|
||||
baseorm.BaseOrm
|
||||
HostCode string `json:"host_code"` //网站唯一码(主要键)
|
||||
Day int `json:"day"` //年月日(主要键)如 20260310
|
||||
Host string `json:"host"` //域名
|
||||
TotalCount int64 `json:"total_count"` //总请求数(PV)
|
||||
AttackCount int64 `json:"attack_count"` //攻击拦截数
|
||||
NormalCount int64 `json:"normal_count"` //正常放行数
|
||||
TrafficIn int64 `json:"traffic_in"` //入站流量(bytes)
|
||||
TrafficOut int64 `json:"traffic_out"` //出站流量(bytes)
|
||||
TotalTimeSpent int64 `json:"total_time_spent"` //总响应时间(ms),用于计算平均值
|
||||
HostCode string `gorm:"size:64" json:"host_code"` //网站唯一码(主要键)
|
||||
Day int `json:"day"` //年月日(主要键)如 20260310
|
||||
Host string `gorm:"size:255" json:"host"` //域名
|
||||
TotalCount int64 `json:"total_count"` //总请求数(PV)
|
||||
AttackCount int64 `json:"attack_count"` //攻击拦截数
|
||||
NormalCount int64 `json:"normal_count"` //正常放行数
|
||||
TrafficIn int64 `json:"traffic_in"` //入站流量(bytes)
|
||||
TrafficOut int64 `json:"traffic_out"` //出站流量(bytes)
|
||||
TotalTimeSpent int64 `json:"total_time_spent"` //总响应时间(ms),用于计算平均值
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -112,15 +112,15 @@ type StatsSiteDay struct {
|
||||
*/
|
||||
type StatsSiteHour struct {
|
||||
baseorm.BaseOrm
|
||||
HostCode string `json:"host_code"` //网站唯一码(主要键)
|
||||
HourTime int64 `json:"hour_time"` //整点unix时间戳(秒)(主要键)
|
||||
Host string `json:"host"` //域名
|
||||
TotalCount int64 `json:"total_count"` //总请求数
|
||||
AttackCount int64 `json:"attack_count"` //攻击拦截数
|
||||
NormalCount int64 `json:"normal_count"` //正常放行数
|
||||
TrafficIn int64 `json:"traffic_in"` //入站流量(bytes)
|
||||
TrafficOut int64 `json:"traffic_out"` //出站流量(bytes)
|
||||
TotalTimeSpent int64 `json:"total_time_spent"` //总响应时间(ms)
|
||||
HostCode string `gorm:"size:64" json:"host_code"` //网站唯一码(主要键)
|
||||
HourTime int64 `json:"hour_time"` //整点unix时间戳(秒)(主要键)
|
||||
Host string `gorm:"size:255" json:"host"` //域名
|
||||
TotalCount int64 `json:"total_count"` //总请求数
|
||||
AttackCount int64 `json:"attack_count"` //攻击拦截数
|
||||
NormalCount int64 `json:"normal_count"` //正常放行数
|
||||
TrafficIn int64 `json:"traffic_in"` //入站流量(bytes)
|
||||
TrafficOut int64 `json:"traffic_out"` //出站流量(bytes)
|
||||
TotalTimeSpent int64 `json:"total_time_spent"` //总响应时间(ms)
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -10,12 +10,12 @@ import (
|
||||
*/
|
||||
type SystemConfig struct {
|
||||
baseorm.BaseOrm
|
||||
IsSystem string `json:"is_system"` //是否是系统值
|
||||
ItemClass string `json:"item_class"` //所属分类
|
||||
Item string `json:"item"`
|
||||
Value string `json:"value"`
|
||||
ItemType string `json:"item_type"` //配置类型 普通字符串,可选项
|
||||
Options string `json:"options"` //如果ItemType == 可选项 这个地方有数据
|
||||
HashInfo string `json:"hash_info"`
|
||||
Remarks string `json:"remarks"` //备注
|
||||
IsSystem string `gorm:"size:10" json:"is_system"` //是否是系统值
|
||||
ItemClass string `gorm:"size:100" json:"item_class"` //所属分类
|
||||
Item string `gorm:"size:255" json:"item"`
|
||||
Value string `gorm:"type:text" json:"value"`
|
||||
ItemType string `gorm:"size:50" json:"item_type"` //配置类型 普通字符串,可选项
|
||||
Options string `gorm:"type:text" json:"options"` //如果ItemType == 可选项 这个地方有数据
|
||||
HashInfo string `gorm:"size:255" json:"hash_info"`
|
||||
Remarks string `gorm:"size:500" json:"remarks"` //备注
|
||||
}
|
||||
|
||||
+6
-6
@@ -4,10 +4,10 @@ import "SamWaf/model/baseorm"
|
||||
|
||||
type Task struct {
|
||||
baseorm.BaseOrm
|
||||
TaskName string `json:"task_name"` // 任务名称
|
||||
TaskUnit string `json:"task_unit"` // 单位
|
||||
TaskValue int `json:"task_value"` // 值
|
||||
TaskAt string `json:"task_at"` // 特定时间
|
||||
TaskMethod string `json:"task_method"` //关联方法
|
||||
TaskDaysOfTheWeek string `json:"task_days_of_the_week"` // 周几 在周级别的情况下才起作用
|
||||
TaskName string `gorm:"size:255" json:"task_name"` // 任务名称
|
||||
TaskUnit string `gorm:"size:50" json:"task_unit"` // 单位
|
||||
TaskValue int `json:"task_value"` // 值
|
||||
TaskAt string `gorm:"size:100" json:"task_at"` // 特定时间
|
||||
TaskMethod string `gorm:"size:255" json:"task_method"` //关联方法
|
||||
TaskDaysOfTheWeek string `gorm:"size:100" json:"task_days_of_the_week"` // 周几 在周级别的情况下才起作用
|
||||
}
|
||||
|
||||
+21
-21
@@ -7,25 +7,25 @@ import (
|
||||
// Tunnel 隧道
|
||||
type Tunnel struct {
|
||||
baseorm.BaseOrm
|
||||
Code string `json:"code"` //唯一码
|
||||
Name string `json:"name"` //名称
|
||||
Port string `json:"port"` //端口可以多个,号隔开
|
||||
Protocol string `json:"protocol"` //协议 tcp,udp
|
||||
RemotePort int `json:"remote_port"` //远端端口
|
||||
RemoteIp string `json:"remote_ip"` //远端指定IP
|
||||
AllowIp string `json:"allow_ip"` //允许访问的IP ,号隔开
|
||||
DenyIp string `json:"deny_ip"` //拒绝访问的IP ,号隔开
|
||||
StartStatus int `json:"start_status"` //启动状态 如果是1 启动 ; 如果是0 不启动
|
||||
ConnTimeout int `json:"conn_timeout"` // 连接超时时间,单位秒 60s
|
||||
ReadTimeout int `json:"read_timeout"` // 读取超时时间,单位秒 0 表示不限制
|
||||
WriteTimeout int `json:"write_timeout"` // 写入超时时间,单位秒 0 表示不限制
|
||||
MaxInConnect int `json:"max_in_connect"` // 最大入站连接数 0 表示不限制
|
||||
MaxOutConnect int `json:"max_out_connect"` // 最大出站连接数 0 表示不限制
|
||||
AllowedTimeRanges string `json:"allowed_time_ranges"` // 允许访问的时间段 格式: 08:00-10:00;11:00-12:00 空表示24小时可访问
|
||||
IpVersion string `json:"ip_version"` // IP版本支持: "ipv4" 仅IPv4, "ipv6" 仅IPv6, "both" 同时支持IPv4和IPv6, 默认为"both"
|
||||
Remark string `json:"remark"` //备注
|
||||
SSLStatus int `json:"ssl_status"` // SSL开关 0关闭 1开启
|
||||
SSLCertificate string `json:"ssl_certificate"` // SSL证书路径
|
||||
SSLCertificateKey string `json:"ssl_certificate_key"` // SSL密钥路径
|
||||
SSLProtocols string `json:"ssl_protocols"` // SSL协议版本 如 TLSv1.2 TLSv1.3
|
||||
Code string `gorm:"size:64" json:"code"` //唯一码
|
||||
Name string `gorm:"size:255" json:"name"` //名称
|
||||
Port string `gorm:"size:50" json:"port"` //端口可以多个,号隔开
|
||||
Protocol string `gorm:"size:20" json:"protocol"` //协议 tcp,udp
|
||||
RemotePort int `json:"remote_port"` //远端端口
|
||||
RemoteIp string `gorm:"size:64" json:"remote_ip"` //远端指定IP
|
||||
AllowIp string `gorm:"type:text" json:"allow_ip"` //允许访问的IP ,号隔开
|
||||
DenyIp string `gorm:"type:text" json:"deny_ip"` //拒绝访问的IP ,号隔开
|
||||
StartStatus int `json:"start_status"` //启动状态 如果是1 启动 ; 如果是0 不启动
|
||||
ConnTimeout int `json:"conn_timeout"` // 连接超时时间,单位秒 60s
|
||||
ReadTimeout int `json:"read_timeout"` // 读取超时时间,单位秒 0 表示不限制
|
||||
WriteTimeout int `json:"write_timeout"` // 写入超时时间,单位秒 0 表示不限制
|
||||
MaxInConnect int `json:"max_in_connect"` // 最大入站连接数 0 表示不限制
|
||||
MaxOutConnect int `json:"max_out_connect"` // 最大出站连接数 0 表示不限制
|
||||
AllowedTimeRanges string `gorm:"size:255" json:"allowed_time_ranges"` // 允许访问的时间段 格式: 08:00-10:00;11:00-12:00 空表示24小时可访问
|
||||
IpVersion string `gorm:"size:20" json:"ip_version"` // IP版本支持: "ipv4" 仅IPv4, "ipv6" 仅IPv6, "both" 同时支持IPv4和IPv6, 默认为"both"
|
||||
Remark string `gorm:"size:500" json:"remark"` //备注
|
||||
SSLStatus int `json:"ssl_status"` // SSL开关 0关闭 1开启
|
||||
SSLCertificate string `gorm:"size:500" json:"ssl_certificate"` // SSL证书路径
|
||||
SSLCertificateKey string `gorm:"size:500" json:"ssl_certificate_key"` // SSL密钥路径
|
||||
SSLProtocols string `gorm:"size:100" json:"ssl_protocols"` // SSL协议版本 如 TLSv1.2 TLSv1.3
|
||||
}
|
||||
|
||||
+2
-2
@@ -6,6 +6,6 @@ import (
|
||||
|
||||
type WafSysLog struct {
|
||||
baseorm.BaseOrm
|
||||
OpType string `json:"op_type"` //操作类型
|
||||
OpContent string `json:"op_content"` //操作内容
|
||||
OpType string `gorm:"size:100" json:"op_type"` //操作类型
|
||||
OpContent string `gorm:"type:text" json:"op_content"` //操作内容
|
||||
}
|
||||
|
||||
+66
-2
@@ -210,13 +210,77 @@ func LoadAndInitConfig() {
|
||||
return
|
||||
}
|
||||
fmt.Printf("%s\tINFO\t config updated\n", currentTime)
|
||||
configChanged = false
|
||||
}
|
||||
|
||||
fmt.Printf("%s\tINFO\tuser_code:%s ,soft_id:%s\n",
|
||||
currentTime, global.GWAF_USER_CODE, global.GWAF_TENANT_ID)
|
||||
|
||||
// 注册数据库方言(默认 SQLite;Milestone 2 起按 GWAF_DB_DRIVER 分流)
|
||||
dialect.Register(&dialect.SQLiteDialect{})
|
||||
// ── 数据库 driver 配置 ──────────────────────────────────────────────
|
||||
if config.IsSet("database.driver") {
|
||||
global.GWAF_DB_DRIVER = config.GetString("database.driver")
|
||||
} else {
|
||||
config.Set("database.driver", global.GWAF_DB_DRIVER) // 写入默认值 sqlite
|
||||
configChanged = true
|
||||
}
|
||||
|
||||
if global.GWAF_DB_DRIVER == "mysql" {
|
||||
// MySQL 连接参数(有默认值,仅在 config.yml 有对应键时才覆盖)
|
||||
if config.IsSet("database.mysql.host") {
|
||||
global.GWAF_MYSQL_HOST = config.GetString("database.mysql.host")
|
||||
}
|
||||
if config.IsSet("database.mysql.port") {
|
||||
global.GWAF_MYSQL_PORT = config.GetInt("database.mysql.port")
|
||||
}
|
||||
if config.IsSet("database.mysql.user") {
|
||||
global.GWAF_MYSQL_USER = config.GetString("database.mysql.user")
|
||||
}
|
||||
if config.IsSet("database.mysql.password") {
|
||||
global.GWAF_MYSQL_PASSWORD = config.GetString("database.mysql.password")
|
||||
}
|
||||
if config.IsSet("database.mysql.charset") {
|
||||
global.GWAF_MYSQL_CHARSET = config.GetString("database.mysql.charset")
|
||||
}
|
||||
if config.IsSet("database.mysql.core_db") {
|
||||
global.GWAF_MYSQL_CORE_DB = config.GetString("database.mysql.core_db")
|
||||
}
|
||||
if config.IsSet("database.mysql.log_db") {
|
||||
global.GWAF_MYSQL_LOG_DB = config.GetString("database.mysql.log_db")
|
||||
}
|
||||
if config.IsSet("database.mysql.stats_db") {
|
||||
global.GWAF_MYSQL_STATS_DB = config.GetString("database.mysql.stats_db")
|
||||
}
|
||||
if config.IsSet("database.mysql.max_open_conns") {
|
||||
global.GWAF_MYSQL_MAX_OPEN_CONNS = config.GetInt("database.mysql.max_open_conns")
|
||||
}
|
||||
if config.IsSet("database.mysql.max_idle_conns") {
|
||||
global.GWAF_MYSQL_MAX_IDLE_CONNS = config.GetInt("database.mysql.max_idle_conns")
|
||||
}
|
||||
if config.IsSet("database.mysql.conn_max_lifetime_minutes") {
|
||||
global.GWAF_MYSQL_CONN_MAX_LIFETIME_MINUTES = config.GetInt("database.mysql.conn_max_lifetime_minutes")
|
||||
}
|
||||
}
|
||||
|
||||
// 只有在配置发生变化时才写入文件(提前写,dialect 注册在下方)
|
||||
if configChanged {
|
||||
err := config.WriteConfig()
|
||||
if err != nil {
|
||||
fmt.Printf("%s\tERROR\twrite config failed:%v\n", currentTime, err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("%s\tINFO\t config updated\n", currentTime)
|
||||
configChanged = false // 已写,重置标志
|
||||
}
|
||||
|
||||
// 注册数据库方言
|
||||
switch global.GWAF_DB_DRIVER {
|
||||
case "mysql":
|
||||
dialect.Register(&dialect.MySQLDialect{})
|
||||
fmt.Printf("%s\tINFO\t数据库驱动: MySQL (%s:%d)\n",
|
||||
currentTime, global.GWAF_MYSQL_HOST, global.GWAF_MYSQL_PORT)
|
||||
default:
|
||||
dialect.Register(&dialect.SQLiteDialect{})
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateIpWhitelist 更新IP白名单配置
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
package dialect
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// MySQLDialect implements DBDialect for MySQL 8.0+ databases.
|
||||
type MySQLDialect struct{}
|
||||
|
||||
func (d *MySQLDialect) Name() string { return "mysql" }
|
||||
func (d *MySQLDialect) IsFileBased() bool { return false }
|
||||
func (d *MySQLDialect) SupportsBackup() bool { return false }
|
||||
func (d *MySQLDialect) SupportsRepair() bool { return false }
|
||||
|
||||
func (d *MySQLDialect) ForceIndexClause(table, idx string) string {
|
||||
return fmt.Sprintf("%s FORCE INDEX (%s)", table, idx)
|
||||
}
|
||||
|
||||
func (d *MySQLDialect) FormatTimeWithOffset(colExpr string, offsetMin int) string {
|
||||
sign := "+"
|
||||
h := offsetMin / 60
|
||||
m := offsetMin % 60
|
||||
if offsetMin < 0 {
|
||||
sign = "-"
|
||||
h = -h
|
||||
m = -m
|
||||
}
|
||||
tz := fmt.Sprintf("%s%02d:%02d", sign, h, m)
|
||||
return fmt.Sprintf("DATE_FORMAT(CONVERT_TZ(%s,'+00:00','%s'),'%%Y-%%m-%%d %%H:%%i:%%s')", colExpr, tz)
|
||||
}
|
||||
|
||||
func (d *MySQLDialect) RenameTable(db *gorm.DB, src, dst string) error {
|
||||
return db.Exec(fmt.Sprintf("RENAME TABLE `%s` TO `%s`", src, dst)).Error
|
||||
}
|
||||
|
||||
func (d *MySQLDialect) ListTables(db *gorm.DB) ([]string, error) {
|
||||
var tables []string
|
||||
err := db.Raw(
|
||||
"SELECT TABLE_NAME FROM information_schema.TABLES " +
|
||||
"WHERE TABLE_SCHEMA = DATABASE() AND TABLE_TYPE = 'BASE TABLE' ORDER BY TABLE_NAME",
|
||||
).Scan(&tables).Error
|
||||
return tables, err
|
||||
}
|
||||
|
||||
func (d *MySQLDialect) TableExists(db *gorm.DB, name string) bool {
|
||||
var count int64
|
||||
db.Raw(
|
||||
"SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ?", name,
|
||||
).Scan(&count)
|
||||
return count > 0
|
||||
}
|
||||
|
||||
func (d *MySQLDialect) ColumnInfo(db *gorm.DB, table string) ([]ColumnMeta, error) {
|
||||
rows, err := db.Raw(
|
||||
"SELECT ORDINAL_POSITION - 1, COLUMN_NAME, DATA_TYPE, "+
|
||||
"IF(IS_NULLABLE='NO',1,0), IFNULL(COLUMN_DEFAULT,''), IF(COLUMN_KEY='PRI',1,0) "+
|
||||
"FROM information_schema.COLUMNS "+
|
||||
"WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? ORDER BY ORDINAL_POSITION",
|
||||
table,
|
||||
).Rows()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var cols []ColumnMeta
|
||||
for rows.Next() {
|
||||
var c ColumnMeta
|
||||
var notNull, pk int
|
||||
if err := rows.Scan(&c.Cid, &c.Name, &c.Type, ¬Null, &c.DefaultVal, &pk); err == nil {
|
||||
c.NotNull = notNull == 1
|
||||
c.PrimaryKey = pk > 0
|
||||
cols = append(cols, c)
|
||||
}
|
||||
}
|
||||
return cols, nil
|
||||
}
|
||||
|
||||
func (d *MySQLDialect) IndexInfo(db *gorm.DB, table string) ([]IndexMeta, error) {
|
||||
rows, err := db.Raw(
|
||||
"SELECT INDEX_NAME, NON_UNIQUE, SEQ_IN_INDEX - 1, COLUMN_NAME "+
|
||||
"FROM information_schema.STATISTICS "+
|
||||
"WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? "+
|
||||
"ORDER BY INDEX_NAME, SEQ_IN_INDEX",
|
||||
table,
|
||||
).Rows()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
idxMap := make(map[string]*IndexMeta)
|
||||
var order []string
|
||||
for rows.Next() {
|
||||
var idxName, colName string
|
||||
var nonUnique, seqNo int
|
||||
if err := rows.Scan(&idxName, &nonUnique, &seqNo, &colName); err != nil {
|
||||
continue
|
||||
}
|
||||
if _, ok := idxMap[idxName]; !ok {
|
||||
idxMap[idxName] = &IndexMeta{Name: idxName, Unique: nonUnique == 0}
|
||||
order = append(order, idxName)
|
||||
}
|
||||
idxMap[idxName].Columns = append(idxMap[idxName].Columns, IndexColumnMeta{SeqNo: seqNo, Name: colName})
|
||||
}
|
||||
|
||||
result := make([]IndexMeta, 0, len(order))
|
||||
for _, name := range order {
|
||||
result = append(result, *idxMap[name])
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (d *MySQLDialect) CollectMetrics(db *gorm.DB, name, path string) (*DBMetrics, error) {
|
||||
if db == nil {
|
||||
return nil, fmt.Errorf("database connection is nil: %s", name)
|
||||
}
|
||||
m := &DBMetrics{
|
||||
DatabaseName: name,
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get sql.DB failed: %v", err)
|
||||
}
|
||||
stats := sqlDB.Stats()
|
||||
m.ConnectionCount = stats.OpenConnections
|
||||
m.MaxConnections = stats.MaxOpenConnections
|
||||
m.IdleConnections = stats.Idle
|
||||
m.InUseConnections = stats.InUse
|
||||
|
||||
db.Raw("SELECT @@VERSION").Scan(&m.EngineVersion)
|
||||
|
||||
// Data and index size from information_schema
|
||||
row := db.Raw(
|
||||
"SELECT COALESCE(SUM(DATA_LENGTH),0)/1024/1024, COALESCE(SUM(INDEX_LENGTH),0)/1024/1024 " +
|
||||
"FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE()",
|
||||
).Row()
|
||||
_ = row.Scan(&m.DataSizeMB, &m.IndexSizeMB)
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// BuildMySQLDSN returns a MySQL DSN string. Always includes parseTime=True and loc=Local
|
||||
// so customtype.JsonTime deserialises correctly.
|
||||
func BuildMySQLDSN(host string, port int, user, password, dbName, charset string) string {
|
||||
return fmt.Sprintf(
|
||||
"%s:%s@tcp(%s:%d)/%s?charset=%s&parseTime=True&loc=Local&timeout=10s&readTimeout=30s&writeTimeout=30s",
|
||||
user, password, host, port, dbName, charset,
|
||||
)
|
||||
}
|
||||
|
||||
// BuildMySQLRootDSN returns a DSN that connects to the server without selecting a database,
|
||||
// used for CREATE DATABASE IF NOT EXISTS.
|
||||
func BuildMySQLRootDSN(host string, port int, user, password, charset string) string {
|
||||
return fmt.Sprintf(
|
||||
"%s:%s@tcp(%s:%d)/?charset=%s&parseTime=True&loc=Local&timeout=10s&readTimeout=30s&writeTimeout=30s",
|
||||
user, password, host, port, charset,
|
||||
)
|
||||
}
|
||||
|
||||
// mysqlQuote wraps a MySQL identifier in back-ticks.
|
||||
func mysqlQuote(name string) string {
|
||||
return "`" + strings.ReplaceAll(name, "`", "``") + "`"
|
||||
}
|
||||
@@ -31,6 +31,9 @@ import (
|
||||
)
|
||||
|
||||
func InitCoreDb(currentDir string) (bool, error) {
|
||||
if dialect.Get().Name() == "mysql" {
|
||||
return InitCoreDbMySQL()
|
||||
}
|
||||
if currentDir == "" {
|
||||
currentDir = utils.GetCurrentDir()
|
||||
}
|
||||
@@ -120,6 +123,9 @@ func InitCoreDb(currentDir string) (bool, error) {
|
||||
}
|
||||
|
||||
func InitLogDb(currentDir string) (bool, error) {
|
||||
if dialect.Get().Name() == "mysql" {
|
||||
return InitLogDbMySQL()
|
||||
}
|
||||
if currentDir == "" {
|
||||
currentDir = utils.GetCurrentDir()
|
||||
}
|
||||
@@ -201,6 +207,10 @@ func InitLogDb(currentDir string) (bool, error) {
|
||||
|
||||
// 手工切换日志数据源
|
||||
func InitManaulLogDb(currentDir string, custFileName string) {
|
||||
if dialect.Get().Name() != "sqlite" {
|
||||
// MySQL 模式下所有日志写入同一个库,无需手动切换分库
|
||||
return
|
||||
}
|
||||
if currentDir == "" {
|
||||
currentDir = utils.GetCurrentDir()
|
||||
}
|
||||
@@ -248,6 +258,9 @@ func InitManaulLogDb(currentDir string, custFileName string) {
|
||||
}
|
||||
|
||||
func InitStatsDb(currentDir string) (bool, error) {
|
||||
if dialect.Get().Name() == "mysql" {
|
||||
return InitStatsDbMySQL()
|
||||
}
|
||||
if currentDir == "" {
|
||||
currentDir = utils.GetCurrentDir()
|
||||
}
|
||||
|
||||
+31
-13
@@ -152,6 +152,22 @@ func RunCoreDBMigrations(db *gorm.DB) error {
|
||||
)
|
||||
},
|
||||
},
|
||||
// 迁移1b: 同步 MySQL ip_tags 表结构(补全缺失列 + 修复 longtext → varchar,支持建索引)
|
||||
{
|
||||
ID: "202511140001b_fix_mysql_varchar",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if tx.Dialector.Name() != "mysql" {
|
||||
return nil
|
||||
}
|
||||
zlog.Info("迁移 202511140001b: 同步 ip_tags 表结构")
|
||||
if err := tx.AutoMigrate(&model.IPTag{}); err != nil {
|
||||
return fmt.Errorf("同步 ip_tags 表结构失败: %w", err)
|
||||
}
|
||||
zlog.Info("迁移 202511140001b: ip_tags 表结构同步完成")
|
||||
return nil
|
||||
},
|
||||
Rollback: func(tx *gorm.DB) error { return nil },
|
||||
},
|
||||
// 迁移2: 创建索引(幂等操作)
|
||||
{
|
||||
ID: "202511140002_create_core_indexes",
|
||||
@@ -204,13 +220,16 @@ func RunCoreDBMigrations(db *gorm.DB) error {
|
||||
}
|
||||
|
||||
// 创建索引
|
||||
if err := tx.Exec("CREATE INDEX IF NOT EXISTS idx_firewall_ip_block_ip ON firewall_ip_block(ip)").Error; err != nil {
|
||||
if err := safeCreateIndex(tx, "firewall_ip_block", "idx_firewall_ip_block_ip",
|
||||
"CREATE INDEX IF NOT EXISTS idx_firewall_ip_block_ip ON firewall_ip_block(ip)"); err != nil {
|
||||
zlog.Warn("创建索引 idx_firewall_ip_block_ip 失败", "error", err.Error())
|
||||
}
|
||||
if err := tx.Exec("CREATE INDEX IF NOT EXISTS idx_firewall_ip_block_status ON firewall_ip_block(status)").Error; err != nil {
|
||||
if err := safeCreateIndex(tx, "firewall_ip_block", "idx_firewall_ip_block_status",
|
||||
"CREATE INDEX IF NOT EXISTS idx_firewall_ip_block_status ON firewall_ip_block(status)"); err != nil {
|
||||
zlog.Warn("创建索引 idx_firewall_ip_block_status 失败", "error", err.Error())
|
||||
}
|
||||
if err := tx.Exec("CREATE INDEX IF NOT EXISTS idx_firewall_ip_block_expire_time ON firewall_ip_block(expire_time)").Error; err != nil {
|
||||
if err := safeCreateIndex(tx, "firewall_ip_block", "idx_firewall_ip_block_expire_time",
|
||||
"CREATE INDEX IF NOT EXISTS idx_firewall_ip_block_expire_time ON firewall_ip_block(expire_time)"); err != nil {
|
||||
zlog.Warn("创建索引 idx_firewall_ip_block_expire_time 失败", "error", err.Error())
|
||||
}
|
||||
|
||||
@@ -955,8 +974,7 @@ func createCoreIndexes(tx *gorm.DB) error {
|
||||
zlog.Info("开始创建索引", "index", idx.Name, "sql", idx.SQL)
|
||||
indexStartTime := time.Now()
|
||||
|
||||
if err := tx.Exec(idx.SQL).Error; err != nil {
|
||||
// 记录详细的错误信息
|
||||
if err := safeCreateIndex(tx, "ip_tags", idx.Name, idx.SQL); err != nil {
|
||||
errMsg := fmt.Sprintf("创建索引失败 %s: %v (错误类型: %T)", idx.Name, err, err)
|
||||
zlog.Error("索引创建失败详情", "index", idx.Name, "error", err.Error(), "sql", idx.SQL)
|
||||
return fmt.Errorf("%s", errMsg)
|
||||
@@ -983,7 +1001,7 @@ func cleanupDuplicateIPTags(tx *gorm.DB) error {
|
||||
FROM ip_tags
|
||||
GROUP BY user_code, tenant_id, ip, ip_tag
|
||||
HAVING cnt > 1
|
||||
)
|
||||
) AS t
|
||||
`).Scan(&duplicateCount).Error
|
||||
|
||||
if err != nil {
|
||||
@@ -1019,16 +1037,16 @@ func cleanupDuplicateIPTags(tx *gorm.DB) error {
|
||||
func dropCoreIndexes(tx *gorm.DB) error {
|
||||
zlog.Info("开始删除core索引")
|
||||
|
||||
indexes := []string{
|
||||
"uni_iptags_full",
|
||||
"idx_iptag_ip",
|
||||
indexes := []struct{ table, name string }{
|
||||
{"ip_tags", "uni_iptags_full"},
|
||||
{"ip_tags", "idx_iptag_ip"},
|
||||
}
|
||||
|
||||
for _, indexName := range indexes {
|
||||
if err := tx.Exec(fmt.Sprintf("DROP INDEX IF EXISTS %s", indexName)).Error; err != nil {
|
||||
zlog.Warn("删除索引失败(可能不存在)", "index", indexName, "error", err)
|
||||
for _, idx := range indexes {
|
||||
if err := safeDropIndex(tx, idx.table, idx.name); err != nil {
|
||||
zlog.Warn("删除索引失败(可能不存在)", "index", idx.name, "error", err)
|
||||
} else {
|
||||
zlog.Info("索引删除成功", "index", indexName)
|
||||
zlog.Info("索引删除成功", "index", idx.name)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package wafdb
|
||||
|
||||
import (
|
||||
"SamWaf/wafdb/dialect"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// checkIndexExists reports whether indexName exists on tableName.
|
||||
// Uses GORM's dialect-aware Migrator so it works on SQLite, MySQL, and SQL Server.
|
||||
func checkIndexExists(db *gorm.DB, tableName, indexName string) bool {
|
||||
return db.Migrator().HasIndex(tableName, indexName)
|
||||
}
|
||||
|
||||
// safeCreateIndex creates the named index only when it does not already exist.
|
||||
// createSQL must be a SQLite-style "CREATE [UNIQUE] INDEX IF NOT EXISTS ..." statement;
|
||||
// for MySQL the "IF NOT EXISTS" clause is stripped before execution because MySQL
|
||||
// does not support it on CREATE INDEX.
|
||||
func safeCreateIndex(tx *gorm.DB, table, indexName, createSQL string) error {
|
||||
if tx.Migrator().HasIndex(table, indexName) {
|
||||
return nil
|
||||
}
|
||||
execSQL := createSQL
|
||||
if dialect.Get().Name() == "mysql" {
|
||||
execSQL = strings.Replace(createSQL, "IF NOT EXISTS ", "", 1)
|
||||
}
|
||||
return tx.Exec(execSQL).Error
|
||||
}
|
||||
|
||||
// safeDropIndex drops the named index only when it exists.
|
||||
// MySQL requires the table name in the DROP statement; SQLite uses IF NOT EXISTS.
|
||||
func safeDropIndex(tx *gorm.DB, table, indexName string) error {
|
||||
if !tx.Migrator().HasIndex(table, indexName) {
|
||||
return nil
|
||||
}
|
||||
if dialect.Get().Name() == "mysql" {
|
||||
return tx.Exec(fmt.Sprintf("DROP INDEX `%s` ON `%s`", indexName, table)).Error
|
||||
}
|
||||
return tx.Exec(fmt.Sprintf("DROP INDEX IF EXISTS %s", indexName)).Error
|
||||
}
|
||||
+45
-4
@@ -71,6 +71,48 @@ func RunLogDBMigrations(db *gorm.DB) error {
|
||||
)
|
||||
},
|
||||
},
|
||||
// 迁移1b: 同步 MySQL web_logs 表结构(补全缺失列 + 修复 longtext → varchar,支持建索引)
|
||||
{
|
||||
ID: "202511140001b_fix_mysql_varchar",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if tx.Dialector.Name() != "mysql" {
|
||||
return nil
|
||||
}
|
||||
zlog.Info("迁移 202511140001b: 同步 web_logs 表结构")
|
||||
if err := tx.AutoMigrate(&innerbean.WebLog{}); err != nil {
|
||||
return fmt.Errorf("同步 web_logs 表结构失败: %w", err)
|
||||
}
|
||||
zlog.Info("迁移 202511140001b: web_logs 表结构同步完成")
|
||||
return nil
|
||||
},
|
||||
Rollback: func(tx *gorm.DB) error { return nil },
|
||||
},
|
||||
// 迁移1c: 确保 guest_identification 列存在(兼容旧表列名拼写错误 guest_id_entification)
|
||||
{
|
||||
ID: "202511140001c_fix_guest_identification_col",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if tx.Dialector.Name() != "mysql" {
|
||||
return nil
|
||||
}
|
||||
// 检查目标列是否已存在
|
||||
var newColCount int64
|
||||
tx.Raw("SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'web_logs' AND column_name = 'guest_identification'").Scan(&newColCount)
|
||||
if newColCount > 0 {
|
||||
zlog.Info("迁移 202511140001c: guest_identification 列已存在,跳过")
|
||||
return nil
|
||||
}
|
||||
// 检查旧拼写错误列 guest_id_entification 是否存在
|
||||
var oldColCount int64
|
||||
tx.Raw("SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'web_logs' AND column_name = 'guest_id_entification'").Scan(&oldColCount)
|
||||
if oldColCount > 0 {
|
||||
zlog.Info("迁移 202511140001c: 将 guest_id_entification 重命名为 guest_identification(保留数据)")
|
||||
return tx.Exec("ALTER TABLE `web_logs` CHANGE COLUMN `guest_id_entification` `guest_identification` VARCHAR(191) NOT NULL DEFAULT ''").Error
|
||||
}
|
||||
zlog.Info("迁移 202511140001c: 添加 guest_identification 列")
|
||||
return tx.Exec("ALTER TABLE `web_logs` ADD COLUMN `guest_identification` VARCHAR(191) NOT NULL DEFAULT ''").Error
|
||||
},
|
||||
Rollback: func(tx *gorm.DB) error { return nil },
|
||||
},
|
||||
// 迁移2: 创建索引(幂等操作)
|
||||
{
|
||||
ID: "202511140002_create_log_indexes",
|
||||
@@ -278,7 +320,7 @@ func createLogIndexes(tx *gorm.DB) error {
|
||||
},
|
||||
{
|
||||
Name: "idx_web_guest_id_entification",
|
||||
SQL: "CREATE INDEX IF NOT EXISTS idx_web_guest_id_entification ON web_logs (guest_id_entification, day, is_bot, host_code)",
|
||||
SQL: "CREATE INDEX IF NOT EXISTS idx_web_guest_id_entification ON web_logs (guest_identification, day, is_bot, host_code)",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -286,8 +328,7 @@ func createLogIndexes(tx *gorm.DB) error {
|
||||
zlog.Info("开始创建索引", "index", idx.Name, "sql", idx.SQL)
|
||||
indexStartTime := time.Now()
|
||||
|
||||
if err := tx.Exec(idx.SQL).Error; err != nil {
|
||||
// 记录详细的错误信息
|
||||
if err := safeCreateIndex(tx, "web_logs", idx.Name, idx.SQL); err != nil {
|
||||
errMsg := fmt.Sprintf("创建索引失败 %s: %v (错误类型: %T)", idx.Name, err, err)
|
||||
zlog.Error("索引创建失败详情", "index", idx.Name, "error", err.Error(), "sql", idx.SQL)
|
||||
return fmt.Errorf("%s", errMsg)
|
||||
@@ -317,7 +358,7 @@ func dropLogIndexes(tx *gorm.DB) error {
|
||||
}
|
||||
|
||||
for _, indexName := range indexes {
|
||||
if err := tx.Exec(fmt.Sprintf("DROP INDEX IF EXISTS %s", indexName)).Error; err != nil {
|
||||
if err := safeDropIndex(tx, "web_logs", indexName); err != nil {
|
||||
zlog.Warn("删除索引失败(可能不存在)", "index", indexName, "error", err)
|
||||
} else {
|
||||
zlog.Info("索引删除成功", "index", indexName)
|
||||
|
||||
+70
-51
@@ -74,6 +74,27 @@ func RunStatsDBMigrations(db *gorm.DB) error {
|
||||
)
|
||||
},
|
||||
},
|
||||
// 迁移1b: 同步 MySQL stats 各表结构(补全缺失列 + 修复 longtext → varchar,支持建索引)
|
||||
{
|
||||
ID: "202511110001b_fix_mysql_varchar",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if tx.Dialector.Name() != "mysql" {
|
||||
return nil
|
||||
}
|
||||
zlog.Info("迁移 202511110001b: 同步 stats 各表结构")
|
||||
if err := tx.AutoMigrate(
|
||||
&model.StatsDay{},
|
||||
&model.StatsIPDay{},
|
||||
&model.StatsIPCityDay{},
|
||||
&model.IPTag{},
|
||||
); err != nil {
|
||||
return fmt.Errorf("同步 stats 表结构失败: %w", err)
|
||||
}
|
||||
zlog.Info("迁移 202511110001b: stats 表结构同步完成")
|
||||
return nil
|
||||
},
|
||||
Rollback: func(tx *gorm.DB) error { return nil },
|
||||
},
|
||||
// 迁移2: 创建索引(幂等操作)
|
||||
{
|
||||
ID: "202511110002_create_indexes",
|
||||
@@ -102,12 +123,18 @@ func RunStatsDBMigrations(db *gorm.DB) error {
|
||||
return fmt.Errorf("创建站点统计表失败: %w", err)
|
||||
}
|
||||
zlog.Info("迁移 202603100001: 创建站点统计索引")
|
||||
sqls := []string{
|
||||
"CREATE INDEX IF NOT EXISTS idx_stats_site_days_lookup ON stats_site_days (host_code, day)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_stats_site_hours_lookup ON stats_site_hours (host_code, hour_time)",
|
||||
siteIdxs := []struct{ name, table, sql string }{
|
||||
{
|
||||
"idx_stats_site_days_lookup", "stats_site_days",
|
||||
"CREATE INDEX IF NOT EXISTS idx_stats_site_days_lookup ON stats_site_days (host_code, day)",
|
||||
},
|
||||
{
|
||||
"idx_stats_site_hours_lookup", "stats_site_hours",
|
||||
"CREATE INDEX IF NOT EXISTS idx_stats_site_hours_lookup ON stats_site_hours (host_code, hour_time)",
|
||||
},
|
||||
}
|
||||
for _, s := range sqls {
|
||||
if err := tx.Exec(s).Error; err != nil {
|
||||
for _, idx := range siteIdxs {
|
||||
if err := safeCreateIndex(tx, idx.table, idx.name, idx.sql); err != nil {
|
||||
return fmt.Errorf("创建站点统计索引失败: %w", err)
|
||||
}
|
||||
}
|
||||
@@ -134,19 +161,20 @@ func RunStatsDBMigrations(db *gorm.DB) error {
|
||||
})
|
||||
|
||||
indexes := []struct {
|
||||
name string
|
||||
sql string
|
||||
name string
|
||||
table string
|
||||
sql string
|
||||
}{
|
||||
{"idx_stats_ip_days_day", "CREATE INDEX IF NOT EXISTS idx_stats_ip_days_day ON stats_ip_days(day)"},
|
||||
{"idx_stats_ip_city_days_day", "CREATE INDEX IF NOT EXISTS idx_stats_ip_city_days_day ON stats_ip_city_days(day)"},
|
||||
{"idx_ip_tags_create_time", "CREATE INDEX IF NOT EXISTS idx_ip_tags_create_time ON ip_tags(create_time)"},
|
||||
{"idx_ip_tags_update_time", "CREATE INDEX IF NOT EXISTS idx_ip_tags_update_time ON ip_tags(update_time)"},
|
||||
{"idx_stats_ip_days_day", "stats_ip_days", "CREATE INDEX IF NOT EXISTS idx_stats_ip_days_day ON stats_ip_days(day)"},
|
||||
{"idx_stats_ip_city_days_day", "stats_ip_city_days", "CREATE INDEX IF NOT EXISTS idx_stats_ip_city_days_day ON stats_ip_city_days(day)"},
|
||||
{"idx_ip_tags_create_time", "ip_tags", "CREATE INDEX IF NOT EXISTS idx_ip_tags_create_time ON ip_tags(create_time)"},
|
||||
{"idx_ip_tags_update_time", "ip_tags", "CREATE INDEX IF NOT EXISTS idx_ip_tags_update_time ON ip_tags(update_time)"},
|
||||
}
|
||||
|
||||
for _, idx := range indexes {
|
||||
zlog.Info("创建索引中...", "index", idx.name)
|
||||
start := time.Now()
|
||||
if err := ddlDB.Exec(idx.sql).Error; err != nil {
|
||||
if err := safeCreateIndex(ddlDB, idx.table, idx.name, idx.sql); err != nil {
|
||||
return fmt.Errorf("创建清理索引 %s 失败: %w", idx.name, err)
|
||||
}
|
||||
zlog.Info("索引创建完成", "index", idx.name, "耗时", time.Since(start).String())
|
||||
@@ -218,20 +246,6 @@ func checkStatsIndexesExist(db *gorm.DB) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// checkIndexExists 检查指定表的索引是否存在(SQLite)
|
||||
func checkIndexExists(db *gorm.DB, tableName, indexName string) bool {
|
||||
var count int64
|
||||
// SQLite 查询索引的方法
|
||||
err := db.Raw("SELECT COUNT(*) FROM sqlite_master WHERE type='index' AND name=? AND tbl_name=?",
|
||||
indexName, tableName).Scan(&count).Error
|
||||
|
||||
if err != nil {
|
||||
zlog.Error("检查索引失败", "error", err, "table", tableName, "index", indexName)
|
||||
return false
|
||||
}
|
||||
return count > 0
|
||||
}
|
||||
|
||||
// createStatsIndexes 创建所有stats索引(幂等操作)
|
||||
func createStatsIndexes(tx *gorm.DB) error {
|
||||
zlog.Info("开始创建stats索引(可能需要几分钟)...")
|
||||
@@ -243,28 +257,34 @@ func createStatsIndexes(tx *gorm.DB) error {
|
||||
}
|
||||
|
||||
indexes := []struct {
|
||||
Name string
|
||||
SQL string
|
||||
Table string
|
||||
Name string
|
||||
SQL string
|
||||
}{
|
||||
{
|
||||
Name: "idx_stats_days_lookup",
|
||||
SQL: "CREATE INDEX IF NOT EXISTS idx_stats_days_lookup ON stats_days (tenant_id, user_code, host_code, type, day)",
|
||||
Table: "stats_days",
|
||||
Name: "idx_stats_days_lookup",
|
||||
SQL: "CREATE INDEX IF NOT EXISTS idx_stats_days_lookup ON stats_days (tenant_id, user_code, host_code, type, day)",
|
||||
},
|
||||
{
|
||||
Name: "idx_stats_ip_days_lookup",
|
||||
SQL: "CREATE INDEX IF NOT EXISTS idx_stats_ip_days_lookup ON stats_ip_days (tenant_id, user_code, host_code, ip, type, day)",
|
||||
Table: "stats_ip_days",
|
||||
Name: "idx_stats_ip_days_lookup",
|
||||
SQL: "CREATE INDEX IF NOT EXISTS idx_stats_ip_days_lookup ON stats_ip_days (tenant_id, user_code, host_code, ip, type, day)",
|
||||
},
|
||||
{
|
||||
Name: "idx_stats_ip_city_days_lookup",
|
||||
SQL: "CREATE INDEX IF NOT EXISTS idx_stats_ip_city_days_lookup ON stats_ip_city_days (tenant_id, user_code, host_code, country, province, city, type, day)",
|
||||
Table: "stats_ip_city_days",
|
||||
Name: "idx_stats_ip_city_days_lookup",
|
||||
SQL: "CREATE INDEX IF NOT EXISTS idx_stats_ip_city_days_lookup ON stats_ip_city_days (tenant_id, user_code, host_code, country, province, city, type, day)",
|
||||
},
|
||||
{
|
||||
Name: "uni_iptags_full",
|
||||
SQL: "CREATE UNIQUE INDEX IF NOT EXISTS uni_iptags_full ON ip_tags (user_code, tenant_id, ip, ip_tag)",
|
||||
Table: "ip_tags",
|
||||
Name: "uni_iptags_full",
|
||||
SQL: "CREATE UNIQUE INDEX IF NOT EXISTS uni_iptags_full ON ip_tags (user_code, tenant_id, ip, ip_tag)",
|
||||
},
|
||||
{
|
||||
Name: "idx_iptag_ip",
|
||||
SQL: "CREATE INDEX IF NOT EXISTS idx_iptag_ip ON ip_tags (user_code, tenant_id, ip)",
|
||||
Table: "ip_tags",
|
||||
Name: "idx_iptag_ip",
|
||||
SQL: "CREATE INDEX IF NOT EXISTS idx_iptag_ip ON ip_tags (user_code, tenant_id, ip)",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -272,8 +292,7 @@ func createStatsIndexes(tx *gorm.DB) error {
|
||||
zlog.Info("开始创建索引", "index", idx.Name, "sql", idx.SQL)
|
||||
indexStartTime := time.Now()
|
||||
|
||||
if err := tx.Exec(idx.SQL).Error; err != nil {
|
||||
// 记录详细的错误信息
|
||||
if err := safeCreateIndex(tx, idx.Table, idx.Name, idx.SQL); err != nil {
|
||||
errMsg := fmt.Sprintf("创建索引失败 %s: %v (错误类型: %T)", idx.Name, err, err)
|
||||
zlog.Error("索引创建失败详情", "index", idx.Name, "error", err.Error(), "sql", idx.SQL)
|
||||
return fmt.Errorf("%s", errMsg)
|
||||
@@ -300,7 +319,7 @@ func cleanupDuplicateIPTagsInStats(tx *gorm.DB) error {
|
||||
FROM ip_tags
|
||||
GROUP BY user_code, tenant_id, ip, ip_tag
|
||||
HAVING cnt > 1
|
||||
)
|
||||
) AS t
|
||||
`).Scan(&duplicateCount).Error
|
||||
|
||||
if err != nil {
|
||||
@@ -336,19 +355,19 @@ func cleanupDuplicateIPTagsInStats(tx *gorm.DB) error {
|
||||
func dropStatsIndexes(tx *gorm.DB) error {
|
||||
zlog.Info("开始删除stats索引")
|
||||
|
||||
indexes := []string{
|
||||
"idx_stats_days_lookup",
|
||||
"idx_stats_ip_days_lookup",
|
||||
"idx_stats_ip_city_days_lookup",
|
||||
"uni_iptags_full",
|
||||
"idx_iptag_ip",
|
||||
indexes := []struct{ table, name string }{
|
||||
{"stats_days", "idx_stats_days_lookup"},
|
||||
{"stats_ip_days", "idx_stats_ip_days_lookup"},
|
||||
{"stats_ip_city_days", "idx_stats_ip_city_days_lookup"},
|
||||
{"ip_tags", "uni_iptags_full"},
|
||||
{"ip_tags", "idx_iptag_ip"},
|
||||
}
|
||||
|
||||
for _, indexName := range indexes {
|
||||
if err := tx.Exec(fmt.Sprintf("DROP INDEX IF EXISTS %s", indexName)).Error; err != nil {
|
||||
zlog.Warn("删除索引失败(可能不存在)", "index", indexName, "error", err)
|
||||
for _, idx := range indexes {
|
||||
if err := safeDropIndex(tx, idx.table, idx.name); err != nil {
|
||||
zlog.Warn("删除索引失败(可能不存在)", "index", idx.name, "error", err)
|
||||
} else {
|
||||
zlog.Info("索引删除成功", "index", indexName)
|
||||
zlog.Info("索引删除成功", "index", idx.name)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,325 @@
|
||||
package wafdb
|
||||
|
||||
// MySQL-specific database initialisation.
|
||||
// This file is compiled for all platforms; the build tag for MySQL driver
|
||||
// activation is handled via the go.mod dependency on gorm.io/driver/mysql.
|
||||
|
||||
import (
|
||||
"SamWaf/common/uuid"
|
||||
"SamWaf/common/zlog"
|
||||
"SamWaf/customtype"
|
||||
"SamWaf/global"
|
||||
"SamWaf/innerbean"
|
||||
"SamWaf/model"
|
||||
"SamWaf/model/baseorm"
|
||||
"SamWaf/wafdb/dialect"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
mysqldriver "gorm.io/driver/mysql"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
)
|
||||
|
||||
// mysqlServerInfo holds a parsed MySQL / MariaDB version string.
|
||||
type mysqlServerInfo struct {
|
||||
Major int
|
||||
Minor int
|
||||
Patch int
|
||||
IsMariaDB bool
|
||||
Raw string
|
||||
}
|
||||
|
||||
// detectServerInfo queries @@VERSION from an open GORM connection.
|
||||
func detectServerInfo(db *gorm.DB) mysqlServerInfo {
|
||||
var raw string
|
||||
db.Raw("SELECT @@VERSION").Scan(&raw)
|
||||
return parseMySQLVersion(raw)
|
||||
}
|
||||
|
||||
// parseMySQLVersion parses strings like:
|
||||
//
|
||||
// "8.0.32" → MySQL 8.0
|
||||
// "5.7.44-log" → MySQL 5.7
|
||||
// "10.6.12-MariaDB" → MariaDB 10.6
|
||||
func parseMySQLVersion(version string) mysqlServerInfo {
|
||||
info := mysqlServerInfo{Raw: version}
|
||||
info.IsMariaDB = strings.Contains(strings.ToLower(version), "mariadb")
|
||||
parts := strings.SplitN(version, ".", 3)
|
||||
if len(parts) >= 1 {
|
||||
info.Major, _ = strconv.Atoi(leadingDigits(parts[0]))
|
||||
}
|
||||
if len(parts) >= 2 {
|
||||
info.Minor, _ = strconv.Atoi(leadingDigits(parts[1]))
|
||||
}
|
||||
if len(parts) >= 3 {
|
||||
info.Patch, _ = strconv.Atoi(leadingDigits(parts[2]))
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
// leadingDigits returns the leading ASCII digit sequence in s.
|
||||
func leadingDigits(s string) string {
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] < '0' || s[i] > '9' {
|
||||
return s[:i]
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// bestCollation returns the most appropriate utf8mb4 collation for this server:
|
||||
//
|
||||
// MySQL 8.0+ → utf8mb4_0900_ai_ci (default; best Unicode support)
|
||||
// MySQL 5.x / MariaDB → utf8mb4_general_ci (universally available)
|
||||
func (v mysqlServerInfo) bestCollation() string {
|
||||
if !v.IsMariaDB && v.Major >= 8 {
|
||||
return "utf8mb4_0900_ai_ci"
|
||||
}
|
||||
return "utf8mb4_general_ci"
|
||||
}
|
||||
|
||||
// label returns a human-readable flavour string for log messages.
|
||||
func (v mysqlServerInfo) label() string {
|
||||
if v.IsMariaDB {
|
||||
return fmt.Sprintf("MariaDB %d.%d.%d", v.Major, v.Minor, v.Patch)
|
||||
}
|
||||
return fmt.Sprintf("MySQL %d.%d.%d", v.Major, v.Minor, v.Patch)
|
||||
}
|
||||
|
||||
// ensureMySQLDatabase makes sure dbName exists before we open a GORM connection to it.
|
||||
//
|
||||
// Strategy:
|
||||
// 1. Connect to the server without selecting a database, detect the server
|
||||
// version, pick the best collation for that version, then issue
|
||||
// CREATE DATABASE IF NOT EXISTS. Succeeds when the user has CREATE privilege.
|
||||
// 2. If step 1 fails (permission denied, or server refuses the collation for any
|
||||
// reason), try connecting directly to the target database.
|
||||
// • Direct connection OK → database already exists; user just lacks CREATE
|
||||
// privilege — proceed normally.
|
||||
// • Direct connection fails → database does not exist and cannot be created;
|
||||
// log a clear hint with the exact SQL the user must run manually.
|
||||
func ensureMySQLDatabase(dbName string) error {
|
||||
rootDSN := dialect.BuildMySQLRootDSN(
|
||||
global.GWAF_MYSQL_HOST, global.GWAF_MYSQL_PORT,
|
||||
global.GWAF_MYSQL_USER, global.GWAF_MYSQL_PASSWORD,
|
||||
global.GWAF_MYSQL_CHARSET,
|
||||
)
|
||||
|
||||
// Step 1: connect without a database, detect version, auto-create.
|
||||
rootDB, connErr := gorm.Open(mysqldriver.Open(rootDSN), &gorm.Config{
|
||||
Logger: logger.Default.LogMode(logger.Silent),
|
||||
})
|
||||
if connErr == nil {
|
||||
sqlDB, _ := rootDB.DB()
|
||||
defer sqlDB.Close()
|
||||
|
||||
info := detectServerInfo(rootDB)
|
||||
collation := info.bestCollation()
|
||||
createSQL := fmt.Sprintf(
|
||||
"CREATE DATABASE IF NOT EXISTS `%s` CHARACTER SET utf8mb4 COLLATE %s",
|
||||
dbName, collation,
|
||||
)
|
||||
zlog.Info("mysql: 服务器信息", "version", info.label(), "collation", collation)
|
||||
|
||||
if createErr := rootDB.Exec(createSQL).Error; createErr == nil {
|
||||
zlog.Info("mysql: 数据库已就绪(新建或已存在)", "db", dbName)
|
||||
return nil
|
||||
} else {
|
||||
zlog.Warn("mysql: 自动建库失败,尝试直连确认库是否已存在",
|
||||
"db", dbName, "error", createErr)
|
||||
}
|
||||
} else {
|
||||
zlog.Warn("mysql: 无 database 根连接失败,尝试直连目标库",
|
||||
"db", dbName, "error", connErr)
|
||||
}
|
||||
|
||||
// Step 2: fall back — try connecting directly to the target database.
|
||||
directDSN := dialect.BuildMySQLDSN(
|
||||
global.GWAF_MYSQL_HOST, global.GWAF_MYSQL_PORT,
|
||||
global.GWAF_MYSQL_USER, global.GWAF_MYSQL_PASSWORD,
|
||||
dbName, global.GWAF_MYSQL_CHARSET,
|
||||
)
|
||||
directDB, directErr := gorm.Open(mysqldriver.Open(directDSN), &gorm.Config{
|
||||
Logger: logger.Default.LogMode(logger.Silent),
|
||||
})
|
||||
if directErr == nil {
|
||||
directSQLDB, _ := directDB.DB()
|
||||
directSQLDB.Close()
|
||||
zlog.Info("mysql: 数据库已存在,跳过自动建库", "db", dbName)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Step 3: nothing worked — emit actionable guidance and abort.
|
||||
// Use utf8mb4_general_ci in the hint: it works on all MySQL/MariaDB versions.
|
||||
hintSQL := fmt.Sprintf(
|
||||
"CREATE DATABASE IF NOT EXISTS `%s` CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci",
|
||||
dbName,
|
||||
)
|
||||
hint := fmt.Sprintf(
|
||||
"\n========================================================\n"+
|
||||
" 数据库 `%s` 不存在,且当前账户无权自动创建。\n"+
|
||||
" 请用 MySQL root 账户执行以下 SQL,然后重启 SamWaf:\n\n"+
|
||||
" %s;\n"+
|
||||
" GRANT ALL PRIVILEGES ON `%s`.* TO '%s'@'%%';\n"+
|
||||
" FLUSH PRIVILEGES;\n"+
|
||||
"========================================================",
|
||||
dbName, hintSQL, dbName, global.GWAF_MYSQL_USER,
|
||||
)
|
||||
fmt.Println(hint) // also print to stdout so it's visible even without log viewer
|
||||
zlog.Error("mysql: 无法连接或创建数据库", "db", dbName, "hint", hint)
|
||||
return fmt.Errorf("mysql: 数据库 `%s` 不存在且权限不足,请手动建库后重启(详见日志)", dbName)
|
||||
}
|
||||
|
||||
// openMySQLDB opens a GORM connection to the named MySQL database,
|
||||
// applying connection pool settings from global config.
|
||||
func openMySQLDB(dbName string) (*gorm.DB, error) {
|
||||
if err := ensureMySQLDatabase(dbName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dsn := dialect.BuildMySQLDSN(
|
||||
global.GWAF_MYSQL_HOST, global.GWAF_MYSQL_PORT,
|
||||
global.GWAF_MYSQL_USER, global.GWAF_MYSQL_PASSWORD,
|
||||
dbName, global.GWAF_MYSQL_CHARSET,
|
||||
)
|
||||
db, err := gorm.Open(mysqldriver.Open(dsn), &gorm.Config{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("mysql: 打开数据库 %s 失败: %w", dbName, err)
|
||||
}
|
||||
|
||||
sqlDB, _ := db.DB()
|
||||
sqlDB.SetMaxOpenConns(global.GWAF_MYSQL_MAX_OPEN_CONNS)
|
||||
sqlDB.SetMaxIdleConns(global.GWAF_MYSQL_MAX_IDLE_CONNS)
|
||||
sqlDB.SetConnMaxLifetime(time.Duration(global.GWAF_MYSQL_CONN_MAX_LIFETIME_MINUTES) * time.Minute)
|
||||
// 空闲连接最多存活 3 分钟,远小于 MySQL 默认 wait_timeout(8h),
|
||||
// 避免 MySQL 主动关闭后 Go 连接池仍持有死连接,导致 "bad connection"
|
||||
sqlDB.SetConnMaxIdleTime(3 * time.Minute)
|
||||
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// applyGormLogger attaches the custom GormZLogger (and debug mode) to db.
|
||||
func applyGormLogger(db *gorm.DB) *gorm.DB {
|
||||
gormLogger := NewGormZLogger()
|
||||
if global.GWAF_LOG_DEBUG_DB_ENABLE {
|
||||
gormLogger = gormLogger.LogMode(logger.Info).(*GormZLogger)
|
||||
db = db.Session(&gorm.Session{Logger: gormLogger})
|
||||
}
|
||||
return db
|
||||
}
|
||||
|
||||
// InitCoreDbMySQL initialises the MySQL core database.
|
||||
func InitCoreDbMySQL() (bool, error) {
|
||||
if global.GWAF_LOCAL_DB != nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
dbName := global.GWAF_MYSQL_CORE_DB
|
||||
zlog.Info("mysql: 初始化核心数据库", "db", dbName)
|
||||
|
||||
db, err := openMySQLDB(dbName)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
db = applyGormLogger(db)
|
||||
global.GWAF_LOCAL_DB = db
|
||||
|
||||
zlog.Info("开始执行core数据库迁移...")
|
||||
if err := RunCoreDBMigrations(db); err != nil {
|
||||
panic("core database migration failed: " + err.Error())
|
||||
}
|
||||
zlog.Info("开始执行任务初始化迁移...")
|
||||
if err := RunTaskInitMigrations(db); err != nil {
|
||||
panic("task initialization migration failed: " + err.Error())
|
||||
}
|
||||
|
||||
global.GWAF_LOCAL_DB.Callback().Query().Before("gorm:query").Register("tenant_plugin:before_query", before_query)
|
||||
global.GWAF_LOCAL_DB.Callback().Query().Before("gorm:update").Register("tenant_plugin:before_update", before_update)
|
||||
|
||||
db.Where("user_code = ? and rule_status = 999", global.GWAF_USER_CODE).Delete(model.Rules{})
|
||||
pathCoreSql(db)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// InitLogDbMySQL initialises the MySQL log database.
|
||||
func InitLogDbMySQL() (bool, error) {
|
||||
if global.GWAF_LOCAL_LOG_DB != nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
dbName := global.GWAF_MYSQL_LOG_DB
|
||||
zlog.Info("mysql: 初始化日志数据库", "db", dbName)
|
||||
|
||||
db, err := openMySQLDB(dbName)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
db = applyGormLogger(db)
|
||||
global.GWAF_LOCAL_LOG_DB = db
|
||||
|
||||
zlog.Info("开始执行log数据库迁移...")
|
||||
if err := RunLogDBMigrations(db); err != nil {
|
||||
panic("log database migration failed: " + err.Error())
|
||||
}
|
||||
|
||||
global.GWAF_LOCAL_LOG_DB.Callback().Query().Before("gorm:query").Register("tenant_plugin:before_query", before_query)
|
||||
global.GWAF_LOCAL_LOG_DB.Callback().Query().Before("gorm:update").Register("tenant_plugin:before_update", before_update)
|
||||
|
||||
pathLogSql(db)
|
||||
|
||||
// Ensure a ShareDb entry exists for the MySQL log database.
|
||||
var total int64
|
||||
global.GWAF_LOCAL_DB.Model(&model.ShareDb{}).Count(&total)
|
||||
if total == 0 {
|
||||
var logTotal int64
|
||||
global.GWAF_LOCAL_LOG_DB.Model(&innerbean.WebLog{}).Count(&logTotal)
|
||||
|
||||
sharDbBean := model.ShareDb{
|
||||
BaseOrm: baseorm.BaseOrm{
|
||||
Id: uuid.GenUUID(),
|
||||
USER_CODE: global.GWAF_USER_CODE,
|
||||
Tenant_ID: global.GWAF_TENANT_ID,
|
||||
CREATE_TIME: customtype.JsonTime(time.Now()),
|
||||
UPDATE_TIME: customtype.JsonTime(time.Now()),
|
||||
},
|
||||
DbLogicType: "log",
|
||||
StartTime: customtype.JsonTime(time.Now()),
|
||||
EndTime: customtype.JsonTime(time.Now()),
|
||||
FileName: dbName, // MySQL DB name (no .db extension)
|
||||
Cnt: logTotal,
|
||||
}
|
||||
global.GWAF_LOCAL_DB.Create(sharDbBean)
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// InitStatsDbMySQL initialises the MySQL statistics database.
|
||||
func InitStatsDbMySQL() (bool, error) {
|
||||
if global.GWAF_LOCAL_STATS_DB != nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
dbName := global.GWAF_MYSQL_STATS_DB
|
||||
zlog.Info("mysql: 初始化统计数据库", "db", dbName)
|
||||
|
||||
db, err := openMySQLDB(dbName)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
db = applyGormLogger(db)
|
||||
global.GWAF_LOCAL_STATS_DB = db
|
||||
|
||||
zlog.Info("开始执行stats数据库迁移...")
|
||||
if err := RunStatsDBMigrations(db); err != nil {
|
||||
panic("stats database migration failed: " + err.Error())
|
||||
}
|
||||
|
||||
global.GWAF_LOCAL_STATS_DB.Callback().Query().Before("gorm:query").Register("tenant_plugin:before_query", before_query)
|
||||
global.GWAF_LOCAL_STATS_DB.Callback().Query().Before("gorm:update").Register("tenant_plugin:before_update", before_update)
|
||||
|
||||
pathStatsSql(db)
|
||||
return false, nil
|
||||
}
|
||||
Reference in New Issue
Block a user