169 lines
4.4 KiB
Go
169 lines
4.4 KiB
Go
|
|
package handlers
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"strconv"
|
|||
|
|
"strings"
|
|||
|
|
"time"
|
|||
|
|
securityEntities "tyapi-server/internal/domains/security/entities"
|
|||
|
|
"tyapi-server/internal/shared/interfaces"
|
|||
|
|
"tyapi-server/internal/shared/ipgeo"
|
|||
|
|
|
|||
|
|
"github.com/gin-gonic/gin"
|
|||
|
|
"go.uber.org/zap"
|
|||
|
|
"gorm.io/gorm"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// AdminSecurityHandler 管理员安全数据处理器
|
|||
|
|
type AdminSecurityHandler struct {
|
|||
|
|
db *gorm.DB
|
|||
|
|
responseBuilder interfaces.ResponseBuilder
|
|||
|
|
logger *zap.Logger
|
|||
|
|
ipLocator *ipgeo.Locator
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func NewAdminSecurityHandler(
|
|||
|
|
db *gorm.DB,
|
|||
|
|
responseBuilder interfaces.ResponseBuilder,
|
|||
|
|
logger *zap.Logger,
|
|||
|
|
ipLocator *ipgeo.Locator,
|
|||
|
|
) *AdminSecurityHandler {
|
|||
|
|
return &AdminSecurityHandler{
|
|||
|
|
db: db,
|
|||
|
|
responseBuilder: responseBuilder,
|
|||
|
|
logger: logger,
|
|||
|
|
ipLocator: ipLocator,
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (h *AdminSecurityHandler) getIntQuery(c *gin.Context, key string, defaultValue int) int {
|
|||
|
|
if value := c.Query(key); value != "" {
|
|||
|
|
if intValue, err := strconv.Atoi(value); err == nil && intValue > 0 {
|
|||
|
|
return intValue
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return defaultValue
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (h *AdminSecurityHandler) parseRange(c *gin.Context) (time.Time, time.Time, bool) {
|
|||
|
|
startTime := time.Now().Add(-24 * time.Hour)
|
|||
|
|
endTime := time.Now()
|
|||
|
|
|
|||
|
|
if start := strings.TrimSpace(c.Query("start_time")); start != "" {
|
|||
|
|
t, err := time.Parse("2006-01-02 15:04:05", start)
|
|||
|
|
if err != nil {
|
|||
|
|
h.responseBuilder.BadRequest(c, "start_time格式错误,示例:2026-03-19 10:00:00")
|
|||
|
|
return time.Time{}, time.Time{}, false
|
|||
|
|
}
|
|||
|
|
startTime = t
|
|||
|
|
}
|
|||
|
|
if end := strings.TrimSpace(c.Query("end_time")); end != "" {
|
|||
|
|
t, err := time.Parse("2006-01-02 15:04:05", end)
|
|||
|
|
if err != nil {
|
|||
|
|
h.responseBuilder.BadRequest(c, "end_time格式错误,示例:2026-03-19 12:00:00")
|
|||
|
|
return time.Time{}, time.Time{}, false
|
|||
|
|
}
|
|||
|
|
endTime = t
|
|||
|
|
}
|
|||
|
|
return startTime, endTime, true
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ListSuspiciousIPs 获取可疑IP列表
|
|||
|
|
func (h *AdminSecurityHandler) ListSuspiciousIPs(c *gin.Context) {
|
|||
|
|
page := h.getIntQuery(c, "page", 1)
|
|||
|
|
pageSize := h.getIntQuery(c, "page_size", 20)
|
|||
|
|
if pageSize > 100 {
|
|||
|
|
pageSize = 100
|
|||
|
|
}
|
|||
|
|
startTime, endTime, ok := h.parseRange(c)
|
|||
|
|
if !ok {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
ip := strings.TrimSpace(c.Query("ip"))
|
|||
|
|
path := strings.TrimSpace(c.Query("path"))
|
|||
|
|
|
|||
|
|
query := h.db.Model(&securityEntities.SuspiciousIPRecord{}).
|
|||
|
|
Where("created_at >= ? AND created_at <= ?", startTime, endTime)
|
|||
|
|
if ip != "" {
|
|||
|
|
query = query.Where("ip = ?", ip)
|
|||
|
|
}
|
|||
|
|
if path != "" {
|
|||
|
|
query = query.Where("path LIKE ?", "%"+path+"%")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var total int64
|
|||
|
|
if err := query.Count(&total).Error; err != nil {
|
|||
|
|
h.logger.Error("查询可疑IP总数失败", zap.Error(err))
|
|||
|
|
h.responseBuilder.InternalError(c, "查询失败")
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var items []securityEntities.SuspiciousIPRecord
|
|||
|
|
if err := query.Order("created_at DESC").Offset((page - 1) * pageSize).Limit(pageSize).Find(&items).Error; err != nil {
|
|||
|
|
h.logger.Error("查询可疑IP列表失败", zap.Error(err))
|
|||
|
|
h.responseBuilder.InternalError(c, "查询失败")
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
h.responseBuilder.Success(c, gin.H{
|
|||
|
|
"items": items,
|
|||
|
|
"total": total,
|
|||
|
|
}, "获取成功")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type geoStreamRow struct {
|
|||
|
|
IP string `json:"ip"`
|
|||
|
|
Path string `json:"path"`
|
|||
|
|
Count int `json:"count"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// GetSuspiciousIPGeoStream 获取地球请求流数据
|
|||
|
|
func (h *AdminSecurityHandler) GetSuspiciousIPGeoStream(c *gin.Context) {
|
|||
|
|
startTime, endTime, ok := h.parseRange(c)
|
|||
|
|
if !ok {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
topN := h.getIntQuery(c, "top_n", 200)
|
|||
|
|
if topN > 1000 {
|
|||
|
|
topN = 1000
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var rows []geoStreamRow
|
|||
|
|
err := h.db.Model(&securityEntities.SuspiciousIPRecord{}).
|
|||
|
|
Select("ip, path, COUNT(1) as count").
|
|||
|
|
Where("created_at >= ? AND created_at <= ?", startTime, endTime).
|
|||
|
|
Group("ip, path").
|
|||
|
|
Order("count DESC").
|
|||
|
|
Limit(topN).
|
|||
|
|
Scan(&rows).Error
|
|||
|
|
if err != nil {
|
|||
|
|
h.logger.Error("查询地球请求流失败", zap.Error(err))
|
|||
|
|
h.responseBuilder.InternalError(c, "查询失败")
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 目标固定服务器点位(上海)
|
|||
|
|
const serverName = "TYAPI-Server"
|
|||
|
|
const serverLng = 121.4737
|
|||
|
|
const serverLat = 31.2304
|
|||
|
|
|
|||
|
|
result := make([]gin.H, 0, len(rows))
|
|||
|
|
for _, row := range rows {
|
|||
|
|
record := securityEntities.SuspiciousIPRecord{IP: row.IP}
|
|||
|
|
fromName, fromLng, fromLat := h.ipLocator.ToGeoPoint(record)
|
|||
|
|
result = append(result, gin.H{
|
|||
|
|
"from_name": fromName,
|
|||
|
|
"from_lng": fromLng,
|
|||
|
|
"from_lat": fromLat,
|
|||
|
|
"to_name": serverName,
|
|||
|
|
"to_lng": serverLng,
|
|||
|
|
"to_lat": serverLat,
|
|||
|
|
"value": row.Count,
|
|||
|
|
"path": row.Path,
|
|||
|
|
"ip": row.IP,
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
h.responseBuilder.Success(c, result, "获取成功")
|
|||
|
|
}
|