mirror of
https://gitee.com/samwaf/SamWaf.git
synced 2026-10-31 02:01:44 +08:00
Merge pull request #699 from samwafgo/feat_open_platform
feat:add open platform
This commit is contained in:
@@ -54,6 +54,9 @@ type APIGroup struct {
|
||||
WafPluginApi
|
||||
WafLogFileWriteApi
|
||||
WafIPLocationApi
|
||||
WafOPlatformKeyApi
|
||||
WafOPlatformLogApi
|
||||
WafOPlatformDocApi
|
||||
}
|
||||
|
||||
var APIGroupAPP = new(APIGroup)
|
||||
@@ -118,4 +121,7 @@ var (
|
||||
wafSqlQueryService = waf_service.WafSqlQueryServiceApp
|
||||
|
||||
wafFirewallIPBlockService = waf_service.WafFirewallIPBlockServiceApp
|
||||
|
||||
wafOPlatformKeyService = waf_service.WafOPlatformKeyServiceApp
|
||||
wafOPlatformLogService = waf_service.WafOPlatformLogServiceApp
|
||||
)
|
||||
|
||||
@@ -25,6 +25,16 @@ import (
|
||||
type WafHostAPi struct {
|
||||
}
|
||||
|
||||
// AddApi 新增网站防护主机
|
||||
// @Summary 新增网站防护主机
|
||||
// @Description 新增一个网站防护主机配置
|
||||
// @Tags 网站防护-主机管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param data body request.WafHostAddReq true "主机配置"
|
||||
// @Success 200 {object} response.Response{data=string} "添加成功,返回主机code"
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /wafhost/host/add [post]
|
||||
func (w *WafHostAPi) AddApi(c *gin.Context) {
|
||||
var req request.WafHostAddReq
|
||||
err := c.ShouldBindJSON(&req)
|
||||
@@ -96,6 +106,17 @@ func (w *WafHostAPi) BatchCopyConfigApi(c *gin.Context) {
|
||||
response.FailWithMessage("解析失败", c)
|
||||
}
|
||||
}
|
||||
|
||||
// GetDetailApi 获取网站防护主机详情
|
||||
// @Summary 获取网站防护主机详情
|
||||
// @Description 根据 code 获取单个主机的详细配置
|
||||
// @Tags 网站防护-主机管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param code query string true "主机唯一编码"
|
||||
// @Success 200 {object} response.Response{data=model.Hosts} "获取成功"
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /wafhost/host/detail [get]
|
||||
func (w *WafHostAPi) GetDetailApi(c *gin.Context) {
|
||||
var req request.WafHostDetailReq
|
||||
err := c.ShouldBind(&req)
|
||||
@@ -106,6 +127,17 @@ func (w *WafHostAPi) GetDetailApi(c *gin.Context) {
|
||||
response.FailWithMessage("解析失败", c)
|
||||
}
|
||||
}
|
||||
|
||||
// GetListApi 获取网站防护主机列表
|
||||
// @Summary 获取网站防护主机列表
|
||||
// @Description 分页查询网站防护主机列表
|
||||
// @Tags 网站防护-主机管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param data body request.WafHostSearchReq true "分页查询参数"
|
||||
// @Success 200 {object} response.Response{data=response.PageResult} "获取成功"
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /wafhost/host/list [post]
|
||||
func (w *WafHostAPi) GetListApi(c *gin.Context) {
|
||||
var req request.WafHostSearchReq
|
||||
err := c.ShouldBindJSON(&req)
|
||||
|
||||
@@ -24,6 +24,18 @@ import (
|
||||
type WafLogAPi struct {
|
||||
}
|
||||
|
||||
// GetDetailApi 获取攻击日志详情
|
||||
// @Summary 获取攻击日志详情
|
||||
// @Description 根据 req_uuid 获取单条攻击日志详情(含请求体、响应体)
|
||||
// @Tags 日志-攻击日志
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param req_uuid query string true "请求UUID"
|
||||
// @Param current_db_name query string false "数据库名称,默认 local_log"
|
||||
// @Param output_format query string false "输出格式:raw 或 curl"
|
||||
// @Success 200 {object} response.Response "获取成功"
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /waflog/attack/detail [get]
|
||||
func (w *WafLogAPi) GetDetailApi(c *gin.Context) {
|
||||
var req request.WafAttackLogDetailReq
|
||||
err := c.ShouldBind(&req)
|
||||
@@ -39,6 +51,17 @@ func (w *WafLogAPi) GetDetailApi(c *gin.Context) {
|
||||
response.FailWithMessage("解析失败", c)
|
||||
}
|
||||
}
|
||||
|
||||
// GetListApi 获取攻击日志列表
|
||||
// @Summary 获取攻击日志列表
|
||||
// @Description 分页查询攻击日志,支持按主机码、规则、IP、时间范围等过滤
|
||||
// @Tags 日志-攻击日志
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param data body request.WafAttackLogSearch true "查询参数"
|
||||
// @Success 200 {object} response.Response{data=response.PageResult} "获取成功"
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /waflog/attack/list [post]
|
||||
func (w *WafLogAPi) GetListApi(c *gin.Context) {
|
||||
var req request.WafAttackLogSearch
|
||||
err := c.ShouldBindJSON(&req)
|
||||
|
||||
@@ -0,0 +1,667 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"SamWaf/global"
|
||||
"SamWaf/model/common/response"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GinEngineRef 保存 gin.Engine 引用,在 localserver 初始化路由后设置
|
||||
var GinEngineRef *gin.Engine
|
||||
|
||||
type WafOPlatformDocApi struct{}
|
||||
|
||||
// apiRouteInfo API路由文档信息
|
||||
type apiRouteInfo struct {
|
||||
Method string `json:"method"`
|
||||
Path string `json:"path"`
|
||||
Module string `json:"module"`
|
||||
Description string `json:"description"`
|
||||
ParamExample string `json:"param_example"`
|
||||
ResponseExample string `json:"response_example"`
|
||||
CurlExample string `json:"curl_example"`
|
||||
}
|
||||
|
||||
// routeDetail 静态路由注释信息
|
||||
type routeDetail struct {
|
||||
Description string
|
||||
ParamExample string
|
||||
ResponseExample string
|
||||
}
|
||||
|
||||
// apiDocResponse API文档响应结构
|
||||
type apiDocResponse struct {
|
||||
AuthInfo authInfo `json:"auth_info"`
|
||||
BaseURL string `json:"base_url"`
|
||||
TotalCount int `json:"total_count"`
|
||||
Modules map[string][]apiRouteInfo `json:"modules"`
|
||||
}
|
||||
|
||||
type authInfo struct {
|
||||
HeaderName string `json:"header_name"`
|
||||
Description string `json:"description"`
|
||||
Example string `json:"example"`
|
||||
}
|
||||
|
||||
// routeDetailMap 各接口的静态参数说明注册表,key 格式为 "METHOD /path"
|
||||
var routeDetailMap = map[string]routeDetail{
|
||||
// ======== 网站防护-主机管理 ========
|
||||
"POST /api/v1/wafhost/host/list": {
|
||||
Description: "获取网站防护主机列表(分页)",
|
||||
ParamExample: `{"pageIndex":1,"pageSize":10,"code":"","remarks":""}`,
|
||||
ResponseExample: `{"code":0,"data":{"list":[{"code":"abc123","host":"example.com","port":80,"remote_host":"127.0.0.1","remote_port":8080,"start_status":1}],"total":1},"msg":"查询成功"}`,
|
||||
},
|
||||
"POST /api/v1/wafhost/host/add": {
|
||||
Description: "新增网站防护主机",
|
||||
ParamExample: `{"host":"example.com","port":80,"ssl":0,"remote_host":"127.0.0.1","remote_port":8080,"remarks":"备注说明","start_status":1}`,
|
||||
ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`,
|
||||
},
|
||||
"POST /api/v1/wafhost/host/edit": {
|
||||
Description: "编辑网站防护主机配置",
|
||||
ParamExample: `{"code":"abc123","host":"example.com","port":80,"ssl":0,"remote_host":"127.0.0.1","remote_port":8080,"remarks":"新备注","start_status":1}`,
|
||||
ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`,
|
||||
},
|
||||
"GET /api/v1/wafhost/host/detail": {
|
||||
Description: "获取网站防护主机详情",
|
||||
ParamExample: `?code=abc123`,
|
||||
ResponseExample: `{"code":0,"data":{"code":"abc123","host":"example.com","port":80},"msg":"查询成功"}`,
|
||||
},
|
||||
"GET /api/v1/wafhost/host/del": {
|
||||
Description: "删除网站防护主机",
|
||||
ParamExample: `?code=abc123`,
|
||||
ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`,
|
||||
},
|
||||
"POST /api/v1/wafhost/host/guardstatus": {
|
||||
Description: "修改主机防御状态(开启/关闭防御)",
|
||||
ParamExample: `{"code":"abc123","guard_status":1}`,
|
||||
ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`,
|
||||
},
|
||||
"POST /api/v1/wafhost/host/startstatus": {
|
||||
Description: "修改主机启动状态(启动/停止)",
|
||||
ParamExample: `{"code":"abc123","start_status":1}`,
|
||||
ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`,
|
||||
},
|
||||
|
||||
// ======== 网站防护-规则管理 ========
|
||||
"POST /api/v1/wafhost/rule/list": {
|
||||
Description: "获取WAF规则列表(分页)",
|
||||
ParamExample: `{"pageIndex":1,"pageSize":10,"host_code":"abc123","rule_name":""}`,
|
||||
ResponseExample: `{"code":0,"data":{"list":[{"code":"rule001","rule_code":"v4-xxx","rule_status":1}],"total":1},"msg":"查询成功"}`,
|
||||
},
|
||||
"POST /api/v1/wafhost/rule/add": {
|
||||
Description: "新增WAF规则",
|
||||
ParamExample: `{"rule_code":"v4-xxx","rule_json":"{...}","is_manual_rule":0,"rule_content":"","rule_status":1}`,
|
||||
ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`,
|
||||
},
|
||||
"POST /api/v1/wafhost/rule/edit": {
|
||||
Description: "编辑WAF规则",
|
||||
ParamExample: `{"code":"rule001","rule_json":"{...}","is_manual_rule":0,"rule_status":1}`,
|
||||
ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`,
|
||||
},
|
||||
"GET /api/v1/wafhost/rule/del": {
|
||||
Description: "删除WAF规则",
|
||||
ParamExample: `?code=rule001`,
|
||||
ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`,
|
||||
},
|
||||
"GET /api/v1/wafhost/rule/detail": {
|
||||
Description: "获取WAF规则详情",
|
||||
ParamExample: `?code=rule001`,
|
||||
ResponseExample: `{"code":0,"data":{"code":"rule001","rule_json":"{...}","rule_status":1},"msg":"查询成功"}`,
|
||||
},
|
||||
"POST /api/v1/wafhost/rule/status": {
|
||||
Description: "修改WAF规则启用/禁用状态",
|
||||
ParamExample: `{"code":"rule001","rule_status":1}`,
|
||||
ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`,
|
||||
},
|
||||
|
||||
// ======== 网站防护-IP黑名单 ========
|
||||
"POST /api/v1/wafhost/blockip/list": {
|
||||
Description: "获取IP黑名单列表(分页)",
|
||||
ParamExample: `{"pageIndex":1,"pageSize":10,"host_code":"abc123","ip":""}`,
|
||||
ResponseExample: `{"code":0,"data":{"list":[{"id":"xxx","ip":"1.2.3.4","host_code":"abc123"}],"total":1},"msg":"查询成功"}`,
|
||||
},
|
||||
"POST /api/v1/wafhost/blockip/add": {
|
||||
Description: "新增IP黑名单",
|
||||
ParamExample: `{"host_code":"abc123","ip":"1.2.3.4","remarks":"恶意IP"}`,
|
||||
ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`,
|
||||
},
|
||||
"POST /api/v1/wafhost/blockip/edit": {
|
||||
Description: "编辑IP黑名单",
|
||||
ParamExample: `{"id":"xxx","host_code":"abc123","ip":"1.2.3.5","remarks":"更新备注"}`,
|
||||
ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`,
|
||||
},
|
||||
"GET /api/v1/wafhost/blockip/del": {
|
||||
Description: "删除IP黑名单",
|
||||
ParamExample: `?id=xxx`,
|
||||
ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`,
|
||||
},
|
||||
|
||||
// ======== 网站防护-IP白名单 ========
|
||||
"POST /api/v1/wafhost/allowip/list": {
|
||||
Description: "获取IP白名单列表(分页)",
|
||||
ParamExample: `{"pageIndex":1,"pageSize":10,"host_code":"abc123","ip":""}`,
|
||||
ResponseExample: `{"code":0,"data":{"list":[{"id":"xxx","ip":"192.168.1.1","host_code":"abc123"}],"total":1},"msg":"查询成功"}`,
|
||||
},
|
||||
"POST /api/v1/wafhost/allowip/add": {
|
||||
Description: "新增IP白名单",
|
||||
ParamExample: `{"host_code":"abc123","ip":"192.168.1.100","remarks":"办公室IP"}`,
|
||||
ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`,
|
||||
},
|
||||
"POST /api/v1/wafhost/allowip/edit": {
|
||||
Description: "编辑IP白名单",
|
||||
ParamExample: `{"id":"xxx","host_code":"abc123","ip":"192.168.1.101","remarks":"更新"}`,
|
||||
ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`,
|
||||
},
|
||||
"GET /api/v1/wafhost/allowip/del": {
|
||||
Description: "删除IP白名单",
|
||||
ParamExample: `?id=xxx`,
|
||||
ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`,
|
||||
},
|
||||
|
||||
// ======== 网站防护-URL黑名单 ========
|
||||
"POST /api/v1/wafhost/blockurl/list": {
|
||||
Description: "获取URL黑名单列表(分页)",
|
||||
ParamExample: `{"pageIndex":1,"pageSize":10,"host_code":"abc123"}`,
|
||||
ResponseExample: `{"code":0,"data":{"list":[],"total":0},"msg":"查询成功"}`,
|
||||
},
|
||||
"POST /api/v1/wafhost/blockurl/add": {
|
||||
Description: "新增URL黑名单",
|
||||
ParamExample: `{"host_code":"abc123","url":"/admin/","remarks":"禁止访问后台"}`,
|
||||
ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`,
|
||||
},
|
||||
"GET /api/v1/wafhost/blockurl/del": {
|
||||
Description: "删除URL黑名单",
|
||||
ParamExample: `?id=xxx`,
|
||||
ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`,
|
||||
},
|
||||
|
||||
// ======== 网站防护-URL白名单 ========
|
||||
"POST /api/v1/wafhost/allowurl/list": {
|
||||
Description: "获取URL白名单列表(分页)",
|
||||
ParamExample: `{"pageIndex":1,"pageSize":10,"host_code":"abc123"}`,
|
||||
ResponseExample: `{"code":0,"data":{"list":[],"total":0},"msg":"查询成功"}`,
|
||||
},
|
||||
"POST /api/v1/wafhost/allowurl/add": {
|
||||
Description: "新增URL白名单(绕过WAF检测)",
|
||||
ParamExample: `{"host_code":"abc123","url":"/api/upload","remarks":"上传接口"}`,
|
||||
ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`,
|
||||
},
|
||||
"GET /api/v1/wafhost/allowurl/del": {
|
||||
Description: "删除URL白名单",
|
||||
ParamExample: `?id=xxx`,
|
||||
ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`,
|
||||
},
|
||||
|
||||
// ======== 网站防护-CC防护 ========
|
||||
"POST /api/v1/wafhost/anticc/list": {
|
||||
Description: "获取CC防护规则列表(分页)",
|
||||
ParamExample: `{"pageIndex":1,"pageSize":10,"host_code":"abc123"}`,
|
||||
ResponseExample: `{"code":0,"data":{"list":[],"total":0},"msg":"查询成功"}`,
|
||||
},
|
||||
"POST /api/v1/wafhost/anticc/add": {
|
||||
Description: "新增CC防护规则",
|
||||
ParamExample: `{"host_code":"abc123","url":"/login","threshold":100,"time_window":60,"action":"block"}`,
|
||||
ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`,
|
||||
},
|
||||
"GET /api/v1/wafhost/anticc/del": {
|
||||
Description: "删除CC防护规则",
|
||||
ParamExample: `?id=xxx`,
|
||||
ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`,
|
||||
},
|
||||
|
||||
// ======== 日志-攻击日志 ========
|
||||
"POST /api/v1/waflog/attack/list": {
|
||||
Description: "获取攻击日志列表(分页)",
|
||||
ParamExample: `{"pageIndex":1,"pageSize":10,"host_code":"","src_ip":"","action":"","unix_add_time_begin":"2026-01-01 00:00:00","unix_add_time_end":"2026-12-31 23:59:59"}`,
|
||||
ResponseExample: `{"code":0,"data":{"list":[{"req_uuid":"xxx","src_ip":"1.2.3.4","rule":"SQL注入","action":"block","time_str":"2026-03-06 10:00:00"}],"total":100},"msg":"查询成功"}`,
|
||||
},
|
||||
"POST /api/v1/waflog/attack/detail": {
|
||||
Description: "获取攻击日志详情",
|
||||
ParamExample: `{"req_uuid":"xxx-yyy-zzz","current_db_name":"local_log"}`,
|
||||
ResponseExample: `{"code":0,"data":{"req_uuid":"xxx","src_ip":"1.2.3.4","method":"POST","url":"/login","body":"..."},"msg":"查询成功"}`,
|
||||
},
|
||||
|
||||
// ======== 统计-数据统计 ========
|
||||
"POST /api/v1/stat/dayrangecount": {
|
||||
Description: "按日期范围统计访问量/拦截量",
|
||||
ParamExample: `{"begin_date":"2026-01-01","end_date":"2026-03-06","host_code":""}`,
|
||||
ResponseExample: `{"code":0,"data":{"list":[{"date":"2026-03-06","total":1000,"block":50}]},"msg":"查询成功"}`,
|
||||
},
|
||||
"POST /api/v1/stat/ipcount": {
|
||||
Description: "统计攻击IP排行",
|
||||
ParamExample: `{"begin_date":"2026-01-01","end_date":"2026-03-06","host_code":"","top":10}`,
|
||||
ResponseExample: `{"code":0,"data":{"list":[{"src_ip":"1.2.3.4","count":500}]},"msg":"查询成功"}`,
|
||||
},
|
||||
|
||||
// ======== 系统日志 ========
|
||||
"POST /api/v1/syslog/list": {
|
||||
Description: "获取系统操作日志列表(分页)",
|
||||
ParamExample: `{"pageIndex":1,"pageSize":10,"op_type":"","op_content":""}`,
|
||||
ResponseExample: `{"code":0,"data":{"list":[{"op_type":"login","op_content":"用户登录","time_str":"2026-03-06 10:00:00"}],"total":50},"msg":"查询成功"}`,
|
||||
},
|
||||
|
||||
// ======== 账号管理 ========
|
||||
"POST /api/v1/account/list": {
|
||||
Description: "获取账号列表(分页)",
|
||||
ParamExample: `{"pageIndex":1,"pageSize":10}`,
|
||||
ResponseExample: `{"code":0,"data":{"list":[{"id":"xxx","user_name":"admin","status":1}],"total":1},"msg":"查询成功"}`,
|
||||
},
|
||||
"POST /api/v1/account/add": {
|
||||
Description: "新增账号",
|
||||
ParamExample: `{"user_name":"apiuser","password":"Password@123","status":1}`,
|
||||
ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`,
|
||||
},
|
||||
"GET /api/v1/account/del": {
|
||||
Description: "删除账号",
|
||||
ParamExample: `?id=xxx`,
|
||||
ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`,
|
||||
},
|
||||
|
||||
// ======== 系统配置 ========
|
||||
"POST /api/v1/systemconfig/list": {
|
||||
Description: "获取系统配置项列表",
|
||||
ParamExample: `{"pageIndex":1,"pageSize":50,"config_type":""}`,
|
||||
ResponseExample: `{"code":0,"data":{"list":[{"config_type":"ssl","config_key":"sslorder_expire_day","config_value":"30"}],"total":10},"msg":"查询成功"}`,
|
||||
},
|
||||
"POST /api/v1/systemconfig/updateitem": {
|
||||
Description: "更新单个系统配置项",
|
||||
ParamExample: `{"config_type":"ssl","config_key":"sslorder_expire_day","config_value":"15"}`,
|
||||
ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`,
|
||||
},
|
||||
|
||||
// ======== 系统信息 ========
|
||||
"GET /api/v1/sysinfo/info": {
|
||||
Description: "获取系统运行信息(CPU、内存、版本等)",
|
||||
ParamExample: `(无需参数)`,
|
||||
ResponseExample: `{"code":0,"data":{"cpu_percent":10.5,"mem_percent":40.2,"version":"1.x.x"},"msg":"查询成功"}`,
|
||||
},
|
||||
|
||||
// ======== SSL证书配置 ========
|
||||
"POST /api/v1/wafhost/sslconfig/list": {
|
||||
Description: "获取SSL证书列表(分页)",
|
||||
ParamExample: `{"pageIndex":1,"pageSize":10}`,
|
||||
ResponseExample: `{"code":0,"data":{"list":[{"id":"xxx","domain":"example.com","expire_time":"2027-01-01"}],"total":1},"msg":"查询成功"}`,
|
||||
},
|
||||
"POST /api/v1/wafhost/sslconfig/add": {
|
||||
Description: "上传新增SSL证书",
|
||||
ParamExample: `{"domain":"example.com","cert_content":"-----BEGIN CERTIFICATE-----...","key_content":"-----BEGIN PRIVATE KEY-----..."}`,
|
||||
ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`,
|
||||
},
|
||||
"GET /api/v1/wafhost/sslconfig/del": {
|
||||
Description: "删除SSL证书",
|
||||
ParamExample: `?id=xxx`,
|
||||
ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`,
|
||||
},
|
||||
|
||||
// ======== 负载均衡 ========
|
||||
"POST /api/v1/wafhost/loadbalance/list": {
|
||||
Description: "获取负载均衡配置列表(分页)",
|
||||
ParamExample: `{"pageIndex":1,"pageSize":10,"host_code":"abc123"}`,
|
||||
ResponseExample: `{"code":0,"data":{"list":[],"total":0},"msg":"查询成功"}`,
|
||||
},
|
||||
"POST /api/v1/wafhost/loadbalance/add": {
|
||||
Description: "新增负载均衡后端节点",
|
||||
ParamExample: `{"host_code":"abc123","remote_host":"192.168.1.10","remote_port":8080,"weight":1}`,
|
||||
ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`,
|
||||
},
|
||||
"GET /api/v1/wafhost/loadbalance/del": {
|
||||
Description: "删除负载均衡节点",
|
||||
ParamExample: `?id=xxx`,
|
||||
ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`,
|
||||
},
|
||||
|
||||
// ======== 通知渠道 ========
|
||||
"POST /api/v1/notify/channel/list": {
|
||||
Description: "获取通知渠道列表",
|
||||
ParamExample: `{"pageIndex":1,"pageSize":10}`,
|
||||
ResponseExample: `{"code":0,"data":{"list":[{"id":"xxx","channel_name":"企业微信","channel_type":"wechat_work","status":1}],"total":1},"msg":"查询成功"}`,
|
||||
},
|
||||
"POST /api/v1/notify/channel/add": {
|
||||
Description: "新增通知渠道",
|
||||
ParamExample: `{"channel_name":"钉钉机器人","channel_type":"dingtalk","webhook_url":"https://oapi.dingtalk.com/robot/send?access_token=xxx","status":1}`,
|
||||
ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`,
|
||||
},
|
||||
"GET /api/v1/notify/channel/del": {
|
||||
Description: "删除通知渠道",
|
||||
ParamExample: `?id=xxx`,
|
||||
ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`,
|
||||
},
|
||||
|
||||
// ======== 通知订阅 ========
|
||||
"POST /api/v1/notify/subscription/list": {
|
||||
Description: "获取通知订阅列表",
|
||||
ParamExample: `{"pageIndex":1,"pageSize":10}`,
|
||||
ResponseExample: `{"code":0,"data":{"list":[],"total":0},"msg":"查询成功"}`,
|
||||
},
|
||||
"POST /api/v1/notify/subscription/add": {
|
||||
Description: "新增通知订阅(将事件与渠道关联)",
|
||||
ParamExample: `{"channel_id":"xxx","event_type":"attack_block","host_code":""}`,
|
||||
ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`,
|
||||
},
|
||||
|
||||
// ======== 防火墙IP封禁 ========
|
||||
"POST /api/v1/firewallipblock/list": {
|
||||
Description: "获取防火墙IP封禁规则列表(分页)",
|
||||
ParamExample: `{"pageIndex":1,"pageSize":10}`,
|
||||
ResponseExample: `{"code":0,"data":{"list":[],"total":0},"msg":"查询成功"}`,
|
||||
},
|
||||
"POST /api/v1/firewallipblock/add": {
|
||||
Description: "新增防火墙IP封禁规则",
|
||||
ParamExample: `{"ip":"1.2.3.4","remarks":"攻击者","expire_time":""}`,
|
||||
ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`,
|
||||
},
|
||||
"GET /api/v1/firewallipblock/del": {
|
||||
Description: "删除防火墙IP封禁规则",
|
||||
ParamExample: `?id=xxx`,
|
||||
ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`,
|
||||
},
|
||||
|
||||
// ======== 开放平台-Key管理 ========
|
||||
"POST /api/v1/oplatform/key/list": {
|
||||
Description: "获取OpenAPI Key列表(分页)",
|
||||
ParamExample: `{"pageIndex":1,"pageSize":10,"key_name":""}`,
|
||||
ResponseExample: `{"code":0,"data":{"list":[{"id":"xxx","key_name":"我的Key","api_key":"sk-xxx","status":1,"call_count":100}],"total":1},"msg":"查询成功"}`,
|
||||
},
|
||||
"POST /api/v1/oplatform/key/add": {
|
||||
Description: "新增OpenAPI Key(系统自动生成api_key和api_secret)",
|
||||
ParamExample: `{"key_name":"第三方系统Key","remark":"用于对接XXX系统","rate_limit":1000,"ip_whitelist":"192.168.1.0/24","expire_time":""}`,
|
||||
ResponseExample: `{"code":0,"data":{"api_key":"sk-xxxxxxxx","api_secret":"sk-secret-xxx"},"msg":"操作成功"}`,
|
||||
},
|
||||
"POST /api/v1/oplatform/key/edit": {
|
||||
Description: "编辑OpenAPI Key信息",
|
||||
ParamExample: `{"id":"xxx","key_name":"更新名称","status":1,"remark":"","rate_limit":500,"ip_whitelist":"","expire_time":"2027-01-01 00:00:00"}`,
|
||||
ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`,
|
||||
},
|
||||
"GET /api/v1/oplatform/key/del": {
|
||||
Description: "删除OpenAPI Key",
|
||||
ParamExample: `?id=xxx`,
|
||||
ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`,
|
||||
},
|
||||
"GET /api/v1/oplatform/key/detail": {
|
||||
Description: "获取OpenAPI Key详情",
|
||||
ParamExample: `?id=xxx`,
|
||||
ResponseExample: `{"code":0,"data":{"id":"xxx","key_name":"我的Key","api_key":"sk-xxx","rate_limit":1000,"call_count":500},"msg":"查询成功"}`,
|
||||
},
|
||||
"POST /api/v1/oplatform/key/resetSecret": {
|
||||
Description: "重置OpenAPI Key的Secret(旧Secret立即失效)",
|
||||
ParamExample: `{"id":"xxx"}`,
|
||||
ResponseExample: `{"code":0,"data":{"api_secret":"sk-new-secret-xxx"},"msg":"操作成功"}`,
|
||||
},
|
||||
|
||||
// ======== 开放平台-调用日志 ========
|
||||
"POST /api/v1/oplatform/log/list": {
|
||||
Description: "获取OpenAPI调用日志列表(分页)",
|
||||
ParamExample: `{"pageIndex":1,"pageSize":10,"key_name":"","request_path":""}`,
|
||||
ResponseExample: `{"code":0,"data":{"list":[{"api_key_id":"xxx","key_name":"我的Key","request_path":"/api/v1/wafhost/host/list","request_method":"POST","status_code":200,"duration":12,"time_str":"2026-03-06 10:00:00"}],"total":50},"msg":"查询成功"}`,
|
||||
},
|
||||
"GET /api/v1/oplatform/log/detail": {
|
||||
Description: "获取OpenAPI调用日志详情(含请求体和响应体)",
|
||||
ParamExample: `?id=xxx`,
|
||||
ResponseExample: `{"code":0,"data":{"request_body":"{...}","response_body":"{...}","client_ip":"192.168.1.1"},"msg":"查询成功"}`,
|
||||
},
|
||||
"GET /api/v1/oplatform/log/del": {
|
||||
Description: "删除OpenAPI调用日志记录",
|
||||
ParamExample: `?id=xxx`,
|
||||
ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`,
|
||||
},
|
||||
|
||||
// ======== 敏感词 ========
|
||||
"POST /api/v1/wafhost/sensitive/list": {
|
||||
Description: "获取敏感词列表(分页)",
|
||||
ParamExample: `{"pageIndex":1,"pageSize":10,"host_code":"abc123"}`,
|
||||
ResponseExample: `{"code":0,"data":{"list":[],"total":0},"msg":"查询成功"}`,
|
||||
},
|
||||
"POST /api/v1/wafhost/sensitive/add": {
|
||||
Description: "新增敏感词(用于响应内容过滤)",
|
||||
ParamExample: `{"host_code":"abc123","sensitive_word":"身份证","replace_word":"***","remarks":""}`,
|
||||
ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`,
|
||||
},
|
||||
"GET /api/v1/wafhost/sensitive/del": {
|
||||
Description: "删除敏感词",
|
||||
ParamExample: `?id=xxx`,
|
||||
ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`,
|
||||
},
|
||||
|
||||
// ======== IP失败封禁 ========
|
||||
"POST /api/v1/wafhost/ipfailure/list": {
|
||||
Description: "获取IP失败封禁规则列表(分页)",
|
||||
ParamExample: `{"pageIndex":1,"pageSize":10,"host_code":"abc123"}`,
|
||||
ResponseExample: `{"code":0,"data":{"list":[],"total":0},"msg":"查询成功"}`,
|
||||
},
|
||||
"POST /api/v1/wafhost/ipfailure/add": {
|
||||
Description: "新增IP失败次数封禁规则",
|
||||
ParamExample: `{"host_code":"abc123","url":"/login","fail_count":5,"time_window":300,"block_time":3600,"remarks":"登录失败封禁"}`,
|
||||
ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`,
|
||||
},
|
||||
|
||||
// ======== WAF引擎 ========
|
||||
"GET /api/v1/wafhost/engine/status": {
|
||||
Description: "获取WAF引擎运行状态",
|
||||
ParamExample: `(无需参数)`,
|
||||
ResponseExample: `{"code":0,"data":{"status":"running","host_count":5},"msg":"查询成功"}`,
|
||||
},
|
||||
"POST /api/v1/wafhost/engine/restart": {
|
||||
Description: "重启WAF引擎(重新加载所有规则和配置)",
|
||||
ParamExample: `{}`,
|
||||
ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`,
|
||||
},
|
||||
|
||||
// ======== 系统监控 ========
|
||||
"GET /api/v1/monitor/info": {
|
||||
Description: "获取实时系统监控数据(CPU、内存、网络流量)",
|
||||
ParamExample: `(无需参数)`,
|
||||
ResponseExample: `{"code":0,"data":{"cpu":15.2,"mem":40.5,"net_in":1024,"net_out":2048},"msg":"查询成功"}`,
|
||||
},
|
||||
|
||||
// ======== 缓存规则 ========
|
||||
"POST /api/v1/wafhost/cacherule/list": {
|
||||
Description: "获取缓存规则列表(分页)",
|
||||
ParamExample: `{"pageIndex":1,"pageSize":10,"host_code":"abc123"}`,
|
||||
ResponseExample: `{"code":0,"data":{"list":[],"total":0},"msg":"查询成功"}`,
|
||||
},
|
||||
"POST /api/v1/wafhost/cacherule/add": {
|
||||
Description: "新增缓存规则",
|
||||
ParamExample: `{"host_code":"abc123","url_pattern":"/static/","cache_time":3600,"remarks":"静态资源缓存"}`,
|
||||
ResponseExample: `{"code":0,"data":{},"msg":"操作成功"}`,
|
||||
},
|
||||
}
|
||||
|
||||
// routeModuleMap 路由路径前缀到模块名的映射
|
||||
var routeModuleMap = map[string]string{
|
||||
"/api/v1/wafhost/host": "网站防护-主机管理",
|
||||
"/api/v1/wafhost/rule": "网站防护-规则管理",
|
||||
"/api/v1/wafhost/allowip": "网站防护-IP白名单",
|
||||
"/api/v1/wafhost/allowurl": "网站防护-URL白名单",
|
||||
"/api/v1/wafhost/ldpurl": "网站防护-隐私保护URL",
|
||||
"/api/v1/wafhost/blockip": "网站防护-IP黑名单",
|
||||
"/api/v1/wafhost/blockurl": "网站防护-URL黑名单",
|
||||
"/api/v1/wafhost/anticc": "网站防护-CC防护",
|
||||
"/api/v1/wafhost/ipfailure": "网站防护-IP失败封禁",
|
||||
"/api/v1/wafhost/sensitive": "网站防护-敏感词",
|
||||
"/api/v1/wafhost/loadbalance": "网站防护-负载均衡",
|
||||
"/api/v1/wafhost/sslconfig": "SSL-证书配置",
|
||||
"/api/v1/wafhost/sslorder": "SSL-证书申请",
|
||||
"/api/v1/wafhost/sslexpire": "SSL-证书过期",
|
||||
"/api/v1/wafhost/httpauthbase": "网站防护-HTTP认证",
|
||||
"/api/v1/wafhost/blockingpage": "网站防护-拦截页面",
|
||||
"/api/v1/wafhost/cacherule": "网站防护-缓存规则",
|
||||
"/api/v1/wafhost/otp": "安全-OTP双因素",
|
||||
"/api/v1/waflog/attack": "日志-攻击日志",
|
||||
"/api/v1/stat": "统计-数据统计",
|
||||
"/api/v1/wafhost/engine": "引擎-WAF引擎",
|
||||
"/api/v1/account": "账号管理",
|
||||
"/api/v1/accountlog": "账号日志",
|
||||
"/api/v1/syslog": "系统日志",
|
||||
"/api/v1/sysinfo": "系统信息",
|
||||
"/api/v1/systemconfig": "系统配置",
|
||||
"/api/v1/wafcommon": "公共接口",
|
||||
"/api/v1/onekeymod": "一键模式",
|
||||
"/api/v1/center": "管理中心",
|
||||
"/api/v1/license": "License管理",
|
||||
"/api/v1/batchtask": "批量任务",
|
||||
"/api/v1/task": "任务管理",
|
||||
"/api/v1/gpt": "AI助手",
|
||||
"/api/v1/analysis": "流量分析",
|
||||
"/api/v1/privateinfo": "隐私信息",
|
||||
"/api/v1/privategroup": "隐私分组",
|
||||
"/api/v1/tunnel": "隧道管理",
|
||||
"/api/v1/vpconfig": "VP配置",
|
||||
"/api/v1/file": "文件管理",
|
||||
"/api/v1/monitor": "系统监控",
|
||||
"/api/v1/caserver": "CA服务器",
|
||||
"/api/v1/sqlquery": "SQL查询",
|
||||
"/api/v1/notify/channel": "通知-渠道",
|
||||
"/api/v1/notify/subscription": "通知-订阅",
|
||||
"/api/v1/notify/log": "通知-日志",
|
||||
"/api/v1/firewallipblock": "防火墙-IP封禁",
|
||||
"/api/v1/plugin": "插件管理",
|
||||
"/api/v1/logfilewrite": "日志文件写入",
|
||||
"/api/v1/iplocation": "IP地址库",
|
||||
"/api/v1/oplatform/key": "开放平台-Key管理",
|
||||
"/api/v1/oplatform/log": "开放平台-调用日志",
|
||||
}
|
||||
|
||||
// getModuleByPath 根据路径判断所属模块
|
||||
func getModuleByPath(path string) string {
|
||||
type prefixEntry struct {
|
||||
prefix string
|
||||
module string
|
||||
}
|
||||
var entries []prefixEntry
|
||||
for prefix, module := range routeModuleMap {
|
||||
entries = append(entries, prefixEntry{prefix, module})
|
||||
}
|
||||
sort.Slice(entries, func(i, j int) bool {
|
||||
return len(entries[i].prefix) > len(entries[j].prefix)
|
||||
})
|
||||
for _, entry := range entries {
|
||||
if strings.HasPrefix(path, entry.prefix) {
|
||||
return entry.module
|
||||
}
|
||||
}
|
||||
return "其他"
|
||||
}
|
||||
|
||||
// buildCurlExample 生成 curl 调用示例,优先使用静态参数注册表中的真实参数
|
||||
func buildCurlExample(method, path string, paramExample string) string {
|
||||
baseURL := "http://your-samwaf-host:26666"
|
||||
upper := strings.ToUpper(method)
|
||||
switch upper {
|
||||
case "GET":
|
||||
queryStr := ""
|
||||
if paramExample != "" && strings.HasPrefix(paramExample, "?") {
|
||||
queryStr = paramExample
|
||||
}
|
||||
return fmt.Sprintf(`curl -X GET "%s%s%s" \
|
||||
-H "X-API-Key: your_api_key"`, baseURL, path, queryStr)
|
||||
case "POST":
|
||||
body := `{"pageIndex":1,"pageSize":10}`
|
||||
if paramExample != "" && !strings.HasPrefix(paramExample, "?") && paramExample != "(无需参数)" {
|
||||
body = paramExample
|
||||
}
|
||||
return fmt.Sprintf(`curl -X POST "%s%s" \
|
||||
-H "X-API-Key: your_api_key" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '%s'`, baseURL, path, body)
|
||||
default:
|
||||
return fmt.Sprintf(`curl -X %s "%s%s" \
|
||||
-H "X-API-Key: your_api_key"`, method, baseURL, path)
|
||||
}
|
||||
}
|
||||
|
||||
// shouldExcludeRoute 判断该路由是否应该排除出文档(非业务路由)
|
||||
func shouldExcludeRoute(path string) bool {
|
||||
excludePrefixes := []string{
|
||||
"/api/v1/public/login",
|
||||
"/api/v1/logout",
|
||||
"/api/v1/ws",
|
||||
"/api/v1/center/update",
|
||||
"/api/v1/oplatform/doc",
|
||||
}
|
||||
for _, prefix := range excludePrefixes {
|
||||
if strings.HasPrefix(path, prefix) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetDocApi 获取 API 文档(供管理界面调用,需要 Token 或 API Key 鉴权)
|
||||
func (w *WafOPlatformDocApi) GetDocApi(c *gin.Context) {
|
||||
if GinEngineRef == nil {
|
||||
response.FailWithMessage("文档服务未就绪", c)
|
||||
return
|
||||
}
|
||||
routes := GinEngineRef.Routes()
|
||||
|
||||
modules := make(map[string][]apiRouteInfo)
|
||||
totalCount := 0
|
||||
|
||||
for _, route := range routes {
|
||||
if shouldExcludeRoute(route.Path) {
|
||||
continue
|
||||
}
|
||||
if !strings.HasPrefix(route.Path, "/api/v1/") {
|
||||
continue
|
||||
}
|
||||
|
||||
module := getModuleByPath(route.Path)
|
||||
|
||||
// 查找静态注释信息,key 格式为 "METHOD /path"
|
||||
detailKey := strings.ToUpper(route.Method) + " " + route.Path
|
||||
detail, hasDetail := routeDetailMap[detailKey]
|
||||
|
||||
description := ""
|
||||
paramExample := ""
|
||||
responseExample := ""
|
||||
if hasDetail {
|
||||
description = detail.Description
|
||||
paramExample = detail.ParamExample
|
||||
responseExample = detail.ResponseExample
|
||||
}
|
||||
|
||||
info := apiRouteInfo{
|
||||
Method: route.Method,
|
||||
Path: route.Path,
|
||||
Module: module,
|
||||
Description: description,
|
||||
ParamExample: paramExample,
|
||||
ResponseExample: responseExample,
|
||||
CurlExample: buildCurlExample(route.Method, route.Path, paramExample),
|
||||
}
|
||||
modules[module] = append(modules[module], info)
|
||||
totalCount++
|
||||
}
|
||||
|
||||
// 对每个模块内的路由按路径排序,便于阅读
|
||||
for mod := range modules {
|
||||
sort.Slice(modules[mod], func(i, j int) bool {
|
||||
return modules[mod][i].Path < modules[mod][j].Path
|
||||
})
|
||||
}
|
||||
|
||||
doc := apiDocResponse{
|
||||
AuthInfo: authInfo{
|
||||
HeaderName: "X-API-Key",
|
||||
Description: "在请求头中传入 API Key 进行鉴权,通过管理端「开放平台 > Key管理」创建后使用",
|
||||
Example: "X-API-Key: sk-xxxxxxxxxxxx",
|
||||
},
|
||||
BaseURL: "http://your-samwaf-host:26666",
|
||||
TotalCount: totalCount,
|
||||
Modules: modules,
|
||||
}
|
||||
|
||||
response.OkWithData(doc, c)
|
||||
}
|
||||
|
||||
// GetDocStatusApi 查询开放平台是否已启用(供管理界面 doc.vue 决定是否展示 Swagger UI)
|
||||
func (w *WafOPlatformDocApi) GetDocStatusApi(c *gin.Context) {
|
||||
enabled := int64(0)
|
||||
if global.GCONFIG_OPEN_PLATFORM_ENABLED == 1 {
|
||||
enabled = 1
|
||||
}
|
||||
response.OkWithData(gin.H{"enabled": enabled}, c)
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"SamWaf/model/common/response"
|
||||
"SamWaf/model/request"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type WafOPlatformKeyApi struct{}
|
||||
|
||||
func (w *WafOPlatformKeyApi) AddApi(c *gin.Context) {
|
||||
var req request.WafOPlatformKeyAddReq
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.FailWithMessage("解析失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
if req.KeyName == "" {
|
||||
response.FailWithMessage("Key名称不能为空", c)
|
||||
return
|
||||
}
|
||||
id, apiKey, err := wafOPlatformKeyService.AddApi(req)
|
||||
if err != nil {
|
||||
response.FailWithMessage("添加失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithDetailed(gin.H{"id": id, "api_key": apiKey}, "添加成功", c)
|
||||
}
|
||||
|
||||
func (w *WafOPlatformKeyApi) ModifyApi(c *gin.Context) {
|
||||
var req request.WafOPlatformKeyEditReq
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.FailWithMessage("解析失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
if err := wafOPlatformKeyService.ModifyApi(req); err != nil {
|
||||
response.FailWithMessage("修改失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("修改成功", c)
|
||||
}
|
||||
|
||||
func (w *WafOPlatformKeyApi) DelApi(c *gin.Context) {
|
||||
var req request.WafOPlatformKeyDelReq
|
||||
if err := c.ShouldBindQuery(&req); err != nil {
|
||||
response.FailWithMessage("解析失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
if err := wafOPlatformKeyService.DelApi(req); err != nil {
|
||||
response.FailWithMessage("删除失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("删除成功", c)
|
||||
}
|
||||
|
||||
func (w *WafOPlatformKeyApi) GetDetailApi(c *gin.Context) {
|
||||
var req request.WafOPlatformKeyDetailReq
|
||||
if err := c.ShouldBindQuery(&req); err != nil {
|
||||
response.FailWithMessage("解析失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
bean := wafOPlatformKeyService.GetDetailApi(req)
|
||||
response.OkWithData(bean, c)
|
||||
}
|
||||
|
||||
func (w *WafOPlatformKeyApi) GetListApi(c *gin.Context) {
|
||||
var req request.WafOPlatformKeySearchReq
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.FailWithMessage("解析失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
if req.PageSize == 0 {
|
||||
req.PageSize = 10
|
||||
}
|
||||
if req.PageIndex == 0 {
|
||||
req.PageIndex = 1
|
||||
}
|
||||
list, total, err := wafOPlatformKeyService.GetListApi(req)
|
||||
if err != nil {
|
||||
response.FailWithMessage("查询失败", c)
|
||||
return
|
||||
}
|
||||
response.OkWithDetailed(response.PageResult{
|
||||
List: list,
|
||||
Total: total,
|
||||
PageIndex: req.PageIndex,
|
||||
PageSize: req.PageSize,
|
||||
}, "查询成功", c)
|
||||
}
|
||||
|
||||
func (w *WafOPlatformKeyApi) ResetSecretApi(c *gin.Context) {
|
||||
var req request.WafOPlatformKeyResetSecretReq
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.FailWithMessage("解析失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
newApiKey, err := wafOPlatformKeyService.ResetApiKey(req)
|
||||
if err != nil {
|
||||
response.FailWithMessage("重置失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithDetailed(gin.H{"api_key": newApiKey}, "重置成功", c)
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"SamWaf/model/common/response"
|
||||
"SamWaf/model/request"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type WafOPlatformLogApi struct{}
|
||||
|
||||
func (w *WafOPlatformLogApi) GetDetailApi(c *gin.Context) {
|
||||
var req request.WafOPlatformLogDetailReq
|
||||
if err := c.ShouldBindQuery(&req); err != nil {
|
||||
response.FailWithMessage("解析失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
bean := wafOPlatformLogService.GetDetailApi(req)
|
||||
response.OkWithData(bean, c)
|
||||
}
|
||||
|
||||
func (w *WafOPlatformLogApi) GetListApi(c *gin.Context) {
|
||||
var req request.WafOPlatformLogSearchReq
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.FailWithMessage("解析失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
if req.PageSize == 0 {
|
||||
req.PageSize = 10
|
||||
}
|
||||
if req.PageIndex == 0 {
|
||||
req.PageIndex = 1
|
||||
}
|
||||
list, total, err := wafOPlatformLogService.GetListApi(req)
|
||||
if err != nil {
|
||||
response.FailWithMessage("查询失败", c)
|
||||
return
|
||||
}
|
||||
response.OkWithDetailed(response.PageResult{
|
||||
List: list,
|
||||
Total: total,
|
||||
PageIndex: req.PageIndex,
|
||||
PageSize: req.PageSize,
|
||||
}, "查询成功", c)
|
||||
}
|
||||
|
||||
func (w *WafOPlatformLogApi) DelApi(c *gin.Context) {
|
||||
var req request.WafOPlatformLogDelReq
|
||||
if err := c.ShouldBindQuery(&req); err != nil {
|
||||
response.FailWithMessage("解析失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
if err := wafOPlatformLogService.DelApi(req); err != nil {
|
||||
response.FailWithMessage("删除失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("删除成功", c)
|
||||
}
|
||||
@@ -93,6 +93,14 @@ func (w *WafSysInfoApi) GetAnnouncementApi(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// SysVersionApi 获取系统版本信息
|
||||
// @Summary 获取系统版本信息
|
||||
// @Description 返回当前 SamWaf 的版本号、版本名称及发布状态
|
||||
// @Tags 系统信息
|
||||
// @Produce json
|
||||
// @Success 200 {object} response.Response{data=model.VersionInfo} "获取成功"
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /sysinfo/version [get]
|
||||
func (w *WafSysInfoApi) SysVersionApi(c *gin.Context) {
|
||||
response.OkWithDetailed(model.VersionInfo{
|
||||
Version: global.GWAF_RELEASE_VERSION,
|
||||
|
||||
@@ -805,6 +805,13 @@ func (m *wafSystenService) Graceful() {
|
||||
//https://github.com/pengge/uranus/blob/main/main.go 预备参考
|
||||
}
|
||||
|
||||
// @title SamWaf Open API
|
||||
// @version 1.0
|
||||
// @description SamWaf Web Application Firewall 开放平台 API 文档。需要在请求头携带 X-API-Key 进行鉴权。
|
||||
// @BasePath /api/v1
|
||||
// @securityDefinitions.apikey ApiKeyAuth
|
||||
// @in header
|
||||
// @name X-API-Key
|
||||
func main() {
|
||||
fmt.Println(`
|
||||
==========================================
|
||||
|
||||
@@ -70,6 +70,9 @@ var (
|
||||
GCONFIG_ZEROSSL_EAB_KID string = "" // zerossl eab_kid
|
||||
GCONFIG_ZEROSSL_EAB_HMAC_KEY string = "" // zerossl eab_hmac_key
|
||||
|
||||
// 开放平台配置
|
||||
GCONFIG_OPEN_PLATFORM_ENABLED int64 = 0 // 开放平台开关,默认关闭 有需要的开启 1启用 0禁用
|
||||
|
||||
// 日志文件写入配置 (额外输出,不影响SQLite存储)
|
||||
GCONFIG_LOG_FILE_WRITE_ENABLE int64 = 0 // 日志文件写入开关 (0关闭 1开启)
|
||||
GCONFIG_LOG_FILE_WRITE_PATH string = "logs/access.log" // 日志文件路径
|
||||
|
||||
@@ -54,8 +54,11 @@ require (
|
||||
|
||||
require (
|
||||
dario.cat/mergo v1.0.0 // indirect
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect
|
||||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect
|
||||
github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.13 // indirect
|
||||
github.com/alibabacloud-go/debug v1.0.1 // indirect
|
||||
@@ -88,6 +91,10 @@ require (
|
||||
github.com/go-git/go-git/v5 v5.11.0 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.19.6 // indirect
|
||||
github.com/go-openapi/spec v0.20.4 // indirect
|
||||
github.com/go-openapi/swag v0.19.15 // indirect
|
||||
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.23.0 // indirect
|
||||
@@ -101,6 +108,7 @@ require (
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 // indirect
|
||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||
github.com/klauspost/compress v1.17.8 // indirect
|
||||
@@ -108,6 +116,7 @@ require (
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/magefile/mage v1.15.1-0.20241126214340-bdc92f694516 // indirect
|
||||
github.com/mailru/easyjson v0.7.6 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/miekg/dns v1.1.69 // indirect
|
||||
@@ -137,6 +146,9 @@ require (
|
||||
github.com/spf13/cast v1.10.0 // indirect
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/swaggo/files v1.0.1 // indirect
|
||||
github.com/swaggo/gin-swagger v1.6.1 // indirect
|
||||
github.com/swaggo/swag v1.8.12 // indirect
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.12 // indirect
|
||||
github.com/tidwall/gjson v1.18.0 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
|
||||
@@ -4,11 +4,17 @@ dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
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=
|
||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
|
||||
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
|
||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6 h1:eIf+iGJxdU4U9ypaUfbtOWCsZSbTb8AUHvyPrxu6mAA=
|
||||
github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6/go.mod h1:4EUIoxs/do24zMOGGqYVWgw0s9NtiylnJglOeEB5UJo=
|
||||
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc=
|
||||
@@ -102,6 +108,7 @@ github.com/corazawaf/coraza/v3 v3.3.3 h1:kqjStHAgWqwP5dh7n0vhTOF0a3t+VikNS/EaMiG
|
||||
github.com/corazawaf/coraza/v3 v3.3.3/go.mod h1:xSaXWOhFMSbrV8qOOfBKAyw3aOqfwaSaOy5BgSF8XlA=
|
||||
github.com/corazawaf/libinjection-go v0.2.2 h1:Chzodvb6+NXh6wew5/yhD0Ggioif9ACrQGR4qjTCs1g=
|
||||
github.com/corazawaf/libinjection-go v0.2.2/go.mod h1:OP4TM7xdJ2skyXqNX1AN1wN5nNZEmJNuWbNPOItn7aw=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
|
||||
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -166,6 +173,16 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
|
||||
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs=
|
||||
github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
|
||||
github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M=
|
||||
github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
|
||||
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
@@ -238,6 +255,8 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 h1:9Nu54bhS/H/Kgo2/7xNSUuC5G28VR8ljfrLKU2G4IjU=
|
||||
@@ -276,6 +295,10 @@ github.com/magefile/mage v1.15.1-0.20231118170541-2385abb49a1f h1:iiLWLoibjCL0XN
|
||||
github.com/magefile/mage v1.15.1-0.20231118170541-2385abb49a1f/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||
github.com/magefile/mage v1.15.1-0.20241126214340-bdc92f694516 h1:aAO0L0ulox6m/CLRYvJff+jWXYYCKGpEm3os7dM/Z+M=
|
||||
github.com/magefile/mage v1.15.1-0.20241126214340-bdc92f694516/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
|
||||
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
@@ -389,6 +412,7 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
||||
@@ -399,6 +423,12 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=
|
||||
github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg=
|
||||
github.com/swaggo/gin-swagger v1.6.1 h1:Ri06G4gc9N4t4k8hekMigJ9zKTFSlqj/9paAQCQs7cY=
|
||||
github.com/swaggo/gin-swagger v1.6.1/go.mod h1:LQ+hJStHakCWRiK/YNYtJOu4mR2FP+pxLnILT/qNiTw=
|
||||
github.com/swaggo/swag v1.8.12 h1:pctzkNPu0AlQP2royqX3apjKCQonAnf7KGoxeO4y64w=
|
||||
github.com/swaggo/swag v1.8.12/go.mod h1:lNfm6Gg+oAq3zRJQNEMBE66LIJKM44mxFqhEEgy2its=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.25/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.12 h1:/ABtv4x4FSGxGW0d6Sc88iQn6Up2LalWKwt/Tj7Dtz8=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.12/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||
@@ -520,6 +550,7 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
@@ -563,6 +594,7 @@ golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -665,6 +697,7 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
@@ -682,6 +715,7 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
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.0-20200615113413-eeeca48fe776/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/gorm v1.26.1 h1:ghB2gUI9FkS46luZtn6DLZ0f6ooBJ5IbVej2ENFDjRw=
|
||||
|
||||
@@ -23,6 +23,18 @@ var (
|
||||
func Auth() gin.HandlerFunc {
|
||||
innerName := "Auth"
|
||||
return func(c *gin.Context) {
|
||||
// 优先检查 OpenAPI Key(X-API-Key)
|
||||
apiKey := c.GetHeader("X-API-Key")
|
||||
if apiKey != "" {
|
||||
_, ok := ValidateOpenApiKey(c, apiKey)
|
||||
if !ok {
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
// 获取请求头中 token,实际是一个完整被签名过的 token;a complete, signed token
|
||||
tokenStr := ""
|
||||
loginType := c.GetHeader("X-Login-Type")
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"SamWaf/common/zlog"
|
||||
"SamWaf/global"
|
||||
"SamWaf/model"
|
||||
"SamWaf/model/common/response"
|
||||
"SamWaf/service/waf_service"
|
||||
"bytes"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// rateLimitEntry 限流计数器条目
|
||||
type rateLimitEntry struct {
|
||||
count int64
|
||||
windowEnd time.Time
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
var (
|
||||
openApiKeyService = waf_service.WafOPlatformKeyServiceApp
|
||||
openApiLogService = waf_service.WafOPlatformLogServiceApp
|
||||
rateLimitMap sync.Map // key: api_key_id, value: *rateLimitEntry
|
||||
)
|
||||
|
||||
// bodyWriter 包装 gin.ResponseWriter,用于捕获响应体
|
||||
type bodyWriter struct {
|
||||
gin.ResponseWriter
|
||||
body *bytes.Buffer
|
||||
statusCode int
|
||||
}
|
||||
|
||||
func newBodyWriter(c *gin.Context) *bodyWriter {
|
||||
return &bodyWriter{
|
||||
ResponseWriter: c.Writer,
|
||||
body: &bytes.Buffer{},
|
||||
statusCode: 200,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *bodyWriter) Write(b []byte) (int, error) {
|
||||
w.body.Write(b)
|
||||
return w.ResponseWriter.Write(b)
|
||||
}
|
||||
|
||||
func (w *bodyWriter) WriteHeader(code int) {
|
||||
w.statusCode = code
|
||||
w.ResponseWriter.WriteHeader(code)
|
||||
}
|
||||
|
||||
// ValidateOpenApiKey 校验 API Key 并设置上下文标记,返回 Key 记录和是否通过
|
||||
func ValidateOpenApiKey(c *gin.Context, apiKey string) (model.OPlatformKey, bool) {
|
||||
// 1. 检查开放平台总开关
|
||||
if global.GCONFIG_OPEN_PLATFORM_ENABLED == 0 {
|
||||
response.AuthFailWithMessage("开放平台已关闭", c)
|
||||
return model.OPlatformKey{}, false
|
||||
}
|
||||
|
||||
// 2. 查询 Key 记录
|
||||
keyBean, err := openApiKeyService.GetByApiKey(apiKey)
|
||||
if err != nil {
|
||||
response.AuthFailWithMessage("无效的 API Key", c)
|
||||
return model.OPlatformKey{}, false
|
||||
}
|
||||
|
||||
// 3. 检查启用状态
|
||||
if keyBean.Status != 1 {
|
||||
response.AuthFailWithMessage("API Key 已禁用", c)
|
||||
return model.OPlatformKey{}, false
|
||||
}
|
||||
|
||||
// 4. 检查过期时间
|
||||
if keyBean.ExpireTime != "" {
|
||||
expireTime, parseErr := time.Parse("2006-01-02 15:04:05", keyBean.ExpireTime)
|
||||
if parseErr == nil && time.Now().After(expireTime) {
|
||||
response.AuthFailWithMessage("API Key 已过期", c)
|
||||
return model.OPlatformKey{}, false
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 检查 IP 白名单
|
||||
if keyBean.IPWhitelist != "" {
|
||||
clientIP := c.ClientIP()
|
||||
allowedIPs := strings.Split(keyBean.IPWhitelist, ",")
|
||||
ipAllowed := false
|
||||
for _, allowedIP := range allowedIPs {
|
||||
allowedIP = strings.TrimSpace(allowedIP)
|
||||
if allowedIP == "" {
|
||||
continue
|
||||
}
|
||||
// 支持 CIDR 格式
|
||||
if strings.Contains(allowedIP, "/") {
|
||||
_, ipNet, cidrErr := net.ParseCIDR(allowedIP)
|
||||
if cidrErr == nil && ipNet.Contains(net.ParseIP(clientIP)) {
|
||||
ipAllowed = true
|
||||
break
|
||||
}
|
||||
} else if allowedIP == clientIP {
|
||||
ipAllowed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !ipAllowed {
|
||||
response.AuthFailWithMessage("IP 不在白名单中", c)
|
||||
return model.OPlatformKey{}, false
|
||||
}
|
||||
}
|
||||
|
||||
// 6. 限流检查(按分钟)
|
||||
if keyBean.RateLimit > 0 {
|
||||
entryVal, _ := rateLimitMap.LoadOrStore(keyBean.Id, &rateLimitEntry{
|
||||
windowEnd: time.Now().Add(time.Minute),
|
||||
})
|
||||
entry := entryVal.(*rateLimitEntry)
|
||||
entry.mu.Lock()
|
||||
now := time.Now()
|
||||
if now.After(entry.windowEnd) {
|
||||
// 新的时间窗口
|
||||
entry.count = 0
|
||||
entry.windowEnd = now.Add(time.Minute)
|
||||
}
|
||||
entry.count++
|
||||
currentCount := entry.count
|
||||
entry.mu.Unlock()
|
||||
|
||||
if currentCount > keyBean.RateLimit {
|
||||
response.FailWithMessage("请求频率超限,请稍后再试", c)
|
||||
return model.OPlatformKey{}, false
|
||||
}
|
||||
}
|
||||
|
||||
// 7. 设置 OpenAPI 标记(用于 response 跳过 AES 加密)
|
||||
c.Set("is_openapi", true)
|
||||
c.Set("openapi_key_id", keyBean.Id)
|
||||
c.Set("openapi_key_name", keyBean.KeyName)
|
||||
|
||||
return keyBean, true
|
||||
}
|
||||
|
||||
// OpenApiLogMiddleware 记录 OpenAPI 调用日志的中间件
|
||||
// 在 Auth 之后调用,负责捕获请求/响应并异步写日志
|
||||
func OpenApiLogMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
isOpenApi, exists := c.Get("is_openapi")
|
||||
if !exists || isOpenApi != true {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
startTime := time.Now()
|
||||
path := c.Request.URL.Path
|
||||
method := c.Request.Method
|
||||
clientIP := c.ClientIP()
|
||||
|
||||
keyId, _ := c.Get("openapi_key_id")
|
||||
keyName, _ := c.Get("openapi_key_name")
|
||||
|
||||
// 读取请求体(文件类请求不记录 body)
|
||||
requestBody := ""
|
||||
contentType := c.GetHeader("Content-Type")
|
||||
isFileRequest := strings.Contains(contentType, "multipart") ||
|
||||
strings.Contains(path, "/download") ||
|
||||
strings.Contains(path, "/upload")
|
||||
|
||||
if !isFileRequest && c.Request.Body != nil {
|
||||
bodyBytes, err := io.ReadAll(io.LimitReader(c.Request.Body, 2048))
|
||||
if err == nil {
|
||||
requestBody = string(bodyBytes)
|
||||
// 恢复 body 供后续 handler 使用
|
||||
c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
|
||||
}
|
||||
}
|
||||
|
||||
// 包装 ResponseWriter 以捕获响应
|
||||
bw := newBodyWriter(c)
|
||||
c.Writer = bw
|
||||
|
||||
c.Next()
|
||||
|
||||
duration := time.Since(startTime).Milliseconds()
|
||||
|
||||
// 响应体(截断超过 2KB 的部分)
|
||||
responseBody := ""
|
||||
if !isFileRequest {
|
||||
responseBody = bw.body.String()
|
||||
if len(responseBody) > 2048 {
|
||||
responseBody = responseBody[:2048] + "...(truncated)"
|
||||
}
|
||||
}
|
||||
|
||||
// 异步记录日志
|
||||
logEntry := model.OPlatformLog{
|
||||
ApiKeyId: keyId.(string),
|
||||
KeyName: keyName.(string),
|
||||
RequestPath: path,
|
||||
RequestMethod: method,
|
||||
RequestBody: requestBody,
|
||||
ResponseBody: responseBody,
|
||||
ClientIP: clientIP,
|
||||
StatusCode: bw.statusCode,
|
||||
Duration: duration,
|
||||
TimeStr: startTime.Format("2006-01-02 15:04:05"),
|
||||
}
|
||||
openApiLogService.AddLogAsync(logEntry)
|
||||
|
||||
// 异步更新调用次数
|
||||
go func() {
|
||||
if id, ok := keyId.(string); ok && id != "" {
|
||||
openApiKeyService.IncrCallCount(id)
|
||||
}
|
||||
}()
|
||||
|
||||
zlog.Debug("OpenAPI调用", "path", path, "method", method, "ip", clientIP, "duration_ms", duration)
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,15 @@ const (
|
||||
)
|
||||
|
||||
func Result(code int, data interface{}, msg string, c *gin.Context) {
|
||||
// OpenAPI 请求直接返回明文 JSON,不做 AES 加密
|
||||
if isOpenApi, exists := c.Get("is_openapi"); exists && isOpenApi == true {
|
||||
c.JSON(http.StatusOK, Response{
|
||||
code,
|
||||
data,
|
||||
msg,
|
||||
})
|
||||
return
|
||||
}
|
||||
result, _ := json.Marshal(data) //将数据转换为json
|
||||
encryptStr, _ := wafsec.AesEncrypt(result, global.GWAF_COMMUNICATION_KEY)
|
||||
// 开始时间
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"SamWaf/model/baseorm"
|
||||
)
|
||||
|
||||
/*
|
||||
*
|
||||
开放平台 API Key
|
||||
*/
|
||||
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"` //累计调用次数
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"SamWaf/model/baseorm"
|
||||
)
|
||||
|
||||
/*
|
||||
*
|
||||
开放平台调用日志
|
||||
*/
|
||||
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"` //调用时间字符串
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package request
|
||||
|
||||
import "SamWaf/model/common/request"
|
||||
|
||||
type WafOPlatformKeyAddReq struct {
|
||||
KeyName string `json:"key_name"` //Key 名称
|
||||
Remark string `json:"remark"` //备注
|
||||
RateLimit int64 `json:"rate_limit"` //每分钟限流次数,0不限
|
||||
IPWhitelist string `json:"ip_whitelist"` //IP白名单,逗号分隔,空不限
|
||||
ExpireTime string `json:"expire_time"` //过期时间,空不过期
|
||||
}
|
||||
|
||||
type WafOPlatformKeyDelReq struct {
|
||||
Id string `json:"id" form:"id"` //唯一键
|
||||
}
|
||||
|
||||
type WafOPlatformKeyDetailReq struct {
|
||||
Id string `json:"id" form:"id"` //唯一键
|
||||
}
|
||||
|
||||
type WafOPlatformKeyEditReq struct {
|
||||
Id string `json:"id"`
|
||||
KeyName string `json:"key_name"` //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"` //过期时间,空不过期
|
||||
}
|
||||
|
||||
type WafOPlatformKeySearchReq struct {
|
||||
KeyName string `json:"key_name" form:"key_name"` //Key名称
|
||||
request.PageInfo
|
||||
}
|
||||
|
||||
type WafOPlatformKeyResetSecretReq struct {
|
||||
Id string `json:"id" form:"id"` //唯一键
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package request
|
||||
|
||||
import "SamWaf/model/common/request"
|
||||
|
||||
type WafOPlatformLogDelReq struct {
|
||||
Id string `json:"id" form:"id"` //唯一键
|
||||
}
|
||||
|
||||
type WafOPlatformLogDetailReq struct {
|
||||
Id string `json:"id" form:"id"` //唯一键
|
||||
}
|
||||
|
||||
type WafOPlatformLogSearchReq struct {
|
||||
KeyName string `json:"key_name" form:"key_name"` //Key名称
|
||||
RequestPath string `json:"request_path" form:"request_path"` //请求路径
|
||||
ClientIP string `json:"client_ip" form:"client_ip"` //客户端IP
|
||||
RequestMethod string `json:"request_method" form:"request_method"` //请求方法
|
||||
request.PageInfo
|
||||
}
|
||||
@@ -52,6 +52,9 @@ type ApiGroup struct {
|
||||
PluginRouter
|
||||
LogFileWriteRouter
|
||||
IPLocationRouter
|
||||
WafOPlatformKeyRouter
|
||||
WafOPlatformLogRouter
|
||||
WafOPlatformDocRouter
|
||||
}
|
||||
type PublicApiGroup struct {
|
||||
LoginRouter
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"SamWaf/api"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type WafOPlatformDocRouter struct{}
|
||||
|
||||
func (receiver *WafOPlatformDocRouter) InitOPlatformDocRouter(group *gin.RouterGroup) {
|
||||
docApi := api.APIGroupAPP.WafOPlatformDocApi
|
||||
router := group.Group("")
|
||||
router.GET("/api/v1/oplatform/doc/api", docApi.GetDocApi)
|
||||
router.GET("/api/v1/oplatform/doc/status", docApi.GetDocStatusApi)
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"SamWaf/api"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type WafOPlatformKeyRouter struct{}
|
||||
|
||||
func (receiver *WafOPlatformKeyRouter) InitOPlatformKeyRouter(group *gin.RouterGroup) {
|
||||
oplatformApi := api.APIGroupAPP.WafOPlatformKeyApi
|
||||
router := group.Group("")
|
||||
router.POST("/api/v1/oplatform/key/list", oplatformApi.GetListApi)
|
||||
router.GET("/api/v1/oplatform/key/detail", oplatformApi.GetDetailApi)
|
||||
router.POST("/api/v1/oplatform/key/add", oplatformApi.AddApi)
|
||||
router.POST("/api/v1/oplatform/key/edit", oplatformApi.ModifyApi)
|
||||
router.GET("/api/v1/oplatform/key/del", oplatformApi.DelApi)
|
||||
router.POST("/api/v1/oplatform/key/resetSecret", oplatformApi.ResetSecretApi)
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"SamWaf/api"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type WafOPlatformLogRouter struct{}
|
||||
|
||||
func (receiver *WafOPlatformLogRouter) InitOPlatformLogRouter(group *gin.RouterGroup) {
|
||||
oplatformApi := api.APIGroupAPP.WafOPlatformLogApi
|
||||
router := group.Group("")
|
||||
router.POST("/api/v1/oplatform/log/list", oplatformApi.GetListApi)
|
||||
router.GET("/api/v1/oplatform/log/detail", oplatformApi.GetDetailApi)
|
||||
router.GET("/api/v1/oplatform/log/del", oplatformApi.DelApi)
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package waf_service
|
||||
|
||||
import (
|
||||
"SamWaf/common/uuid"
|
||||
"SamWaf/customtype"
|
||||
"SamWaf/global"
|
||||
"SamWaf/model"
|
||||
"SamWaf/model/baseorm"
|
||||
"SamWaf/model/request"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
type WafOPlatformKeyService struct{}
|
||||
|
||||
var WafOPlatformKeyServiceApp = new(WafOPlatformKeyService)
|
||||
|
||||
// genRandomHex 生成随机十六进制字符串
|
||||
func genRandomHex(n int) string {
|
||||
b := make([]byte, n)
|
||||
_, _ = rand.Read(b)
|
||||
return hex.EncodeToString(b)
|
||||
}
|
||||
|
||||
// maskApiKey 对 API Key 做脱敏,保留前6位和后4位,中间替换为 ****
|
||||
func maskApiKey(key string) string {
|
||||
if len(key) <= 10 {
|
||||
return key
|
||||
}
|
||||
return key[:6] + "****" + key[len(key)-4:]
|
||||
}
|
||||
|
||||
func (receiver *WafOPlatformKeyService) AddApi(req request.WafOPlatformKeyAddReq) (string, string, error) {
|
||||
apiKey := "sk-" + genRandomHex(16)
|
||||
id := uuid.GenUUID()
|
||||
var bean = &model.OPlatformKey{
|
||||
BaseOrm: baseorm.BaseOrm{
|
||||
Id: id,
|
||||
USER_CODE: global.GWAF_USER_CODE,
|
||||
Tenant_ID: global.GWAF_TENANT_ID,
|
||||
CREATE_TIME: customtype.JsonTime(time.Now()),
|
||||
UPDATE_TIME: customtype.JsonTime(time.Now()),
|
||||
},
|
||||
KeyName: req.KeyName,
|
||||
ApiKey: apiKey,
|
||||
Status: 1,
|
||||
Remark: req.Remark,
|
||||
RateLimit: req.RateLimit,
|
||||
IPWhitelist: req.IPWhitelist,
|
||||
ExpireTime: req.ExpireTime,
|
||||
CallCount: 0,
|
||||
}
|
||||
err := global.GWAF_LOCAL_DB.Create(bean).Error
|
||||
return id, apiKey, err
|
||||
}
|
||||
|
||||
func (receiver *WafOPlatformKeyService) ModifyApi(req request.WafOPlatformKeyEditReq) error {
|
||||
editMap := map[string]interface{}{
|
||||
"KeyName": req.KeyName,
|
||||
"Status": req.Status,
|
||||
"Remark": req.Remark,
|
||||
"RateLimit": req.RateLimit,
|
||||
"IPWhitelist": req.IPWhitelist,
|
||||
"ExpireTime": req.ExpireTime,
|
||||
"UPDATE_TIME": customtype.JsonTime(time.Now()),
|
||||
}
|
||||
return global.GWAF_LOCAL_DB.Model(model.OPlatformKey{}).Where("id = ?", req.Id).Updates(editMap).Error
|
||||
}
|
||||
|
||||
func (receiver *WafOPlatformKeyService) DelApi(req request.WafOPlatformKeyDelReq) error {
|
||||
var bean model.OPlatformKey
|
||||
err := global.GWAF_LOCAL_DB.Where("id = ?", req.Id).First(&bean).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return global.GWAF_LOCAL_DB.Where("id = ?", req.Id).Delete(model.OPlatformKey{}).Error
|
||||
}
|
||||
|
||||
func (receiver *WafOPlatformKeyService) GetDetailApi(req request.WafOPlatformKeyDetailReq) model.OPlatformKey {
|
||||
var bean model.OPlatformKey
|
||||
global.GWAF_LOCAL_DB.Where("id = ?", req.Id).Find(&bean)
|
||||
bean.ApiKey = maskApiKey(bean.ApiKey)
|
||||
return bean
|
||||
}
|
||||
|
||||
func (receiver *WafOPlatformKeyService) GetListApi(req request.WafOPlatformKeySearchReq) ([]model.OPlatformKey, int64, error) {
|
||||
var list []model.OPlatformKey
|
||||
var total int64 = 0
|
||||
|
||||
whereField := ""
|
||||
var whereValues []interface{}
|
||||
if len(req.KeyName) > 0 {
|
||||
whereField = "key_name like ?"
|
||||
whereValues = append(whereValues, "%"+req.KeyName+"%")
|
||||
}
|
||||
|
||||
global.GWAF_LOCAL_DB.Model(&model.OPlatformKey{}).Where(whereField, whereValues...).
|
||||
Limit(req.PageSize).Offset(req.PageSize * (req.PageIndex - 1)).Find(&list)
|
||||
global.GWAF_LOCAL_DB.Model(&model.OPlatformKey{}).Where(whereField, whereValues...).Count(&total)
|
||||
|
||||
for i := range list {
|
||||
list[i].ApiKey = maskApiKey(list[i].ApiKey)
|
||||
}
|
||||
return list, total, nil
|
||||
}
|
||||
|
||||
// ResetApiKey 重置 API Key,旧 Key 立即失效
|
||||
func (receiver *WafOPlatformKeyService) ResetApiKey(req request.WafOPlatformKeyResetSecretReq) (string, error) {
|
||||
var bean model.OPlatformKey
|
||||
err := global.GWAF_LOCAL_DB.Where("id = ?", req.Id).First(&bean).Error
|
||||
if err != nil {
|
||||
return "", errors.New("Key不存在")
|
||||
}
|
||||
newApiKey := "sk-" + genRandomHex(16)
|
||||
err = global.GWAF_LOCAL_DB.Model(model.OPlatformKey{}).Where("id = ?", req.Id).Updates(map[string]interface{}{
|
||||
"ApiKey": newApiKey,
|
||||
"UPDATE_TIME": customtype.JsonTime(time.Now()),
|
||||
}).Error
|
||||
return newApiKey, err
|
||||
}
|
||||
|
||||
// GetByApiKey 通过 ApiKey 字符串获取 Key 记录
|
||||
func (receiver *WafOPlatformKeyService) GetByApiKey(apiKey string) (model.OPlatformKey, error) {
|
||||
var bean model.OPlatformKey
|
||||
err := global.GWAF_LOCAL_DB.Where("api_key = ?", apiKey).First(&bean).Error
|
||||
return bean, err
|
||||
}
|
||||
|
||||
// IncrCallCount 增加调用次数并更新最后使用时间(异步调用)
|
||||
func (receiver *WafOPlatformKeyService) IncrCallCount(id string) {
|
||||
now := time.Now()
|
||||
global.GWAF_LOCAL_DB.Exec(
|
||||
"UPDATE o_platform_keys SET call_count = call_count + 1, last_use_time = ?, update_time = ? WHERE id = ?",
|
||||
now.Format("2006-01-02 15:04:05"), customtype.JsonTime(now), id,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package waf_service
|
||||
|
||||
import (
|
||||
"SamWaf/common/uuid"
|
||||
"SamWaf/customtype"
|
||||
"SamWaf/global"
|
||||
"SamWaf/model"
|
||||
"SamWaf/model/request"
|
||||
"time"
|
||||
)
|
||||
|
||||
type WafOPlatformLogService struct{}
|
||||
|
||||
var WafOPlatformLogServiceApp = new(WafOPlatformLogService)
|
||||
|
||||
// AddLog 记录一条调用日志
|
||||
func (receiver *WafOPlatformLogService) AddLog(logEntry model.OPlatformLog) error {
|
||||
logEntry.Id = uuid.GenUUID()
|
||||
logEntry.USER_CODE = global.GWAF_USER_CODE
|
||||
logEntry.Tenant_ID = global.GWAF_TENANT_ID
|
||||
logEntry.CREATE_TIME = customtype.JsonTime(time.Now())
|
||||
logEntry.UPDATE_TIME = customtype.JsonTime(time.Now())
|
||||
if logEntry.TimeStr == "" {
|
||||
logEntry.TimeStr = time.Now().Format("2006-01-02 15:04:05")
|
||||
}
|
||||
return global.GWAF_LOCAL_LOG_DB.Create(&logEntry).Error
|
||||
}
|
||||
|
||||
func (receiver *WafOPlatformLogService) GetDetailApi(req request.WafOPlatformLogDetailReq) model.OPlatformLog {
|
||||
var bean model.OPlatformLog
|
||||
global.GWAF_LOCAL_LOG_DB.Where("id = ?", req.Id).Find(&bean)
|
||||
return bean
|
||||
}
|
||||
|
||||
func (receiver *WafOPlatformLogService) GetListApi(req request.WafOPlatformLogSearchReq) ([]model.OPlatformLog, int64, error) {
|
||||
var list []model.OPlatformLog
|
||||
var total int64 = 0
|
||||
|
||||
whereField := ""
|
||||
var whereValues []interface{}
|
||||
|
||||
if len(req.KeyName) > 0 {
|
||||
if len(whereField) > 0 {
|
||||
whereField += " and "
|
||||
}
|
||||
whereField += "key_name like ?"
|
||||
whereValues = append(whereValues, "%"+req.KeyName+"%")
|
||||
}
|
||||
if len(req.RequestPath) > 0 {
|
||||
if len(whereField) > 0 {
|
||||
whereField += " and "
|
||||
}
|
||||
whereField += "request_path like ?"
|
||||
whereValues = append(whereValues, "%"+req.RequestPath+"%")
|
||||
}
|
||||
if len(req.ClientIP) > 0 {
|
||||
if len(whereField) > 0 {
|
||||
whereField += " and "
|
||||
}
|
||||
whereField += "client_ip = ?"
|
||||
whereValues = append(whereValues, req.ClientIP)
|
||||
}
|
||||
if len(req.RequestMethod) > 0 {
|
||||
if len(whereField) > 0 {
|
||||
whereField += " and "
|
||||
}
|
||||
whereField += "request_method = ?"
|
||||
whereValues = append(whereValues, req.RequestMethod)
|
||||
}
|
||||
|
||||
global.GWAF_LOCAL_LOG_DB.Model(&model.OPlatformLog{}).Where(whereField, whereValues...).
|
||||
Order("create_time desc").
|
||||
Limit(req.PageSize).Offset(req.PageSize * (req.PageIndex - 1)).Find(&list)
|
||||
global.GWAF_LOCAL_LOG_DB.Model(&model.OPlatformLog{}).Where(whereField, whereValues...).Count(&total)
|
||||
|
||||
return list, total, nil
|
||||
}
|
||||
|
||||
func (receiver *WafOPlatformLogService) DelApi(req request.WafOPlatformLogDelReq) error {
|
||||
return global.GWAF_LOCAL_LOG_DB.Where("id = ?", req.Id).Delete(model.OPlatformLog{}).Error
|
||||
}
|
||||
|
||||
// AddLogAsync 异步记录日志,不阻塞主流程
|
||||
func (receiver *WafOPlatformLogService) AddLogAsync(logEntry model.OPlatformLog) {
|
||||
go func() {
|
||||
_ = receiver.AddLog(logEntry)
|
||||
}()
|
||||
}
|
||||
@@ -607,6 +607,51 @@ func RunCoreDBMigrations(db *gorm.DB) error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
// 迁移16: 创建开放平台 API Key 表(不含 api_secret)
|
||||
{
|
||||
ID: "202603060001_add_oplatform_key_table",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
zlog.Info("迁移 202603060001: 创建开放平台 API Key 表")
|
||||
|
||||
if tx.Migrator().HasTable(&model.OPlatformKey{}) {
|
||||
zlog.Info("o_platform_keys 表已存在,执行结构同步")
|
||||
if err := tx.AutoMigrate(&model.OPlatformKey{}); err != nil {
|
||||
return fmt.Errorf("同步 o_platform_keys 表结构失败: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := tx.AutoMigrate(&model.OPlatformKey{}); err != nil {
|
||||
return fmt.Errorf("创建 o_platform_keys 表失败: %w", err)
|
||||
}
|
||||
|
||||
zlog.Info("o_platform_keys 表创建成功")
|
||||
return nil
|
||||
},
|
||||
Rollback: func(tx *gorm.DB) error {
|
||||
zlog.Info("回滚 202603060001: 删除开放平台 API Key 表")
|
||||
return tx.Migrator().DropTable(&model.OPlatformKey{})
|
||||
},
|
||||
},
|
||||
// 迁移17: 移除开放平台 API Key 表中冗余的 api_secret 列
|
||||
{
|
||||
ID: "202603060002_drop_api_secret_from_oplatform_key",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
zlog.Info("迁移 202603060002: 移除 o_platform_keys.api_secret 列")
|
||||
if tx.Migrator().HasColumn(&model.OPlatformKey{}, "api_secret") {
|
||||
if err := tx.Migrator().DropColumn(&model.OPlatformKey{}, "api_secret"); err != nil {
|
||||
return fmt.Errorf("删除 api_secret 列失败: %w", err)
|
||||
}
|
||||
zlog.Info("api_secret 列已删除")
|
||||
} else {
|
||||
zlog.Info("api_secret 列不存在,跳过")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Rollback: func(tx *gorm.DB) error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// 执行迁移
|
||||
|
||||
@@ -136,6 +136,32 @@ func RunLogDBMigrations(db *gorm.DB) error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
// 迁移5: 创建开放平台调用日志表
|
||||
{
|
||||
ID: "202603060001_add_oplatform_log_table",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
zlog.Info("迁移 202603060001: 创建开放平台调用日志表")
|
||||
|
||||
if tx.Migrator().HasTable(&model.OPlatformLog{}) {
|
||||
zlog.Info("o_platform_logs 表已存在,执行结构同步")
|
||||
if err := tx.AutoMigrate(&model.OPlatformLog{}); err != nil {
|
||||
return fmt.Errorf("同步 o_platform_logs 表结构失败: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := tx.AutoMigrate(&model.OPlatformLog{}); err != nil {
|
||||
return fmt.Errorf("创建 o_platform_logs 表失败: %w", err)
|
||||
}
|
||||
|
||||
zlog.Info("o_platform_logs 表创建成功")
|
||||
return nil
|
||||
},
|
||||
Rollback: func(tx *gorm.DB) error {
|
||||
zlog.Info("回滚 202603060001: 删除开放平台调用日志表")
|
||||
return tx.Migrator().DropTable(&model.OPlatformLog{})
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// 执行迁移
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package wafmangeweb
|
||||
|
||||
import (
|
||||
"SamWaf/api"
|
||||
"SamWaf/common/zlog"
|
||||
"SamWaf/global"
|
||||
"SamWaf/middleware"
|
||||
@@ -41,7 +42,7 @@ func (web *WafWebManager) initRouter(r *gin.Engine) {
|
||||
router.PublicApiGroupApp.InitCenterRouter(PublicRouterGroup) //注册中心接收接口
|
||||
|
||||
RouterGroup := r.Group("")
|
||||
RouterGroup.Use(middleware.Auth(), middleware.CenterApi(), middleware.SecApi(), middleware.GinGlobalExceptionMiddleWare(), middleware.IPWhitelist()) //TODO 中心管控 特定
|
||||
RouterGroup.Use(middleware.Auth(), middleware.OpenApiLogMiddleware(), middleware.CenterApi(), middleware.SecApi(), middleware.GinGlobalExceptionMiddleWare(), middleware.IPWhitelist()) //TODO 中心管控 特定
|
||||
{
|
||||
router.ApiGroupApp.InitHostRouter(RouterGroup)
|
||||
router.ApiGroupApp.InitLogRouter(RouterGroup)
|
||||
@@ -93,8 +94,14 @@ func (web *WafWebManager) initRouter(r *gin.Engine) {
|
||||
router.ApiGroupApp.InitFirewallIPBlockRouter(RouterGroup)
|
||||
router.ApiGroupApp.InitLogFileWriteRouter(RouterGroup)
|
||||
router.ApiGroupApp.InitIPLocationRouter(RouterGroup)
|
||||
router.ApiGroupApp.InitOPlatformKeyRouter(RouterGroup)
|
||||
router.ApiGroupApp.InitOPlatformLogRouter(RouterGroup)
|
||||
router.ApiGroupApp.InitOPlatformDocRouter(RouterGroup)
|
||||
}
|
||||
|
||||
// 保存 gin.Engine 引用供 API 文档生成使用
|
||||
api.GinEngineRef = r
|
||||
|
||||
if global.GWAF_RELEASE == "true" {
|
||||
static.Static(r, func(handlers ...gin.HandlerFunc) {
|
||||
r.NoRoute(handlers...)
|
||||
@@ -133,7 +140,7 @@ func (web *WafWebManager) cors() gin.HandlerFunc {
|
||||
// 将该域添加到allow-origin中
|
||||
c.Header("Access-Control-Allow-Origin", origin) //
|
||||
c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE")
|
||||
c.Header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization,X-Token,Remote-Waf-User-Id,OPEN-X-Token,X-Login-Type,X-Mobile-Token")
|
||||
c.Header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization,X-Token,Remote-Waf-User-Id,OPEN-X-Token,X-Login-Type,X-Mobile-Token,X-API-Key")
|
||||
c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Cache-Control, Content-Language, Content-Type")
|
||||
//允许客户端传递校验信息比如 cookie
|
||||
c.Header("Access-Control-Allow-Credentials", "true")
|
||||
|
||||
@@ -171,6 +171,8 @@ func setConfigIntValue(name string, value int64, change int) {
|
||||
case "log_file_write_compress":
|
||||
global.GCONFIG_LOG_FILE_WRITE_COMPRESS = value
|
||||
syncLogFileWriterConfig()
|
||||
case "open_platform_enabled":
|
||||
global.GCONFIG_OPEN_PLATFORM_ENABLED = value
|
||||
default:
|
||||
zlog.Warn("Unknown config item:", name)
|
||||
}
|
||||
@@ -432,4 +434,7 @@ func TaskLoadSetting(initLoad bool) {
|
||||
updateConfigIntItem(initLoad, "logfile", "log_file_write_max_backups", global.GCONFIG_LOG_FILE_WRITE_MAX_BACKUPS, "保留的历史文件数量", "int", "", configMap)
|
||||
updateConfigIntItem(initLoad, "logfile", "log_file_write_max_days", global.GCONFIG_LOG_FILE_WRITE_MAX_DAYS, "保留天数", "int", "", configMap)
|
||||
updateConfigIntItem(initLoad, "logfile", "log_file_write_compress", global.GCONFIG_LOG_FILE_WRITE_COMPRESS, "是否压缩历史文件(0关闭 1开启)", "options", "0|关闭,1|开启", configMap)
|
||||
|
||||
// 开放平台配置
|
||||
updateConfigIntItem(initLoad, "openplatform", "open_platform_enabled", global.GCONFIG_OPEN_PLATFORM_ENABLED, "开放平台开关(1启用 0禁用)", "options", "0|禁用,1|启用", configMap)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user