package models import ( "fmt" "time" "simple_portal/database" ) // 访问动作类型常量 const ( ActionTypeVisit = "visit" // 主页访问 ActionTypeClick = "click" // 卡片点击 ActionTypeSearch = "search" // 搜索 ) // AccessLog 表示一条访问日志记录。 type AccessLog struct { ID int `json:"id"` IP string `json:"ip"` UserAgent string `json:"user_agent"` ActionType string `json:"action_type"` // visit / click / search Detail string `json:"detail"` // 卡片标题 / 搜索关键词 Referer string `json:"referer"` CreatedAt time.Time `json:"created_at"` } // CreateAccessLog 插入一条访问日志。 func CreateAccessLog(ip, userAgent, actionType, detail, referer string) error { _, err := database.DB.Exec( "INSERT INTO access_logs (ip, user_agent, action_type, detail, referer, created_at) VALUES (?, ?, ?, ?, ?, ?)", ip, userAgent, actionType, detail, referer, time.Now().Format("2006-01-02 15:04:05"), ) if err != nil { return fmt.Errorf("failed to create access log: %w", err) } return nil } // AccessLogStats 访问统计数据 type AccessLogStats struct { TodayViews int `json:"today_views"` // 今日浏览次数 TotalViews int `json:"total_views"` // 总浏览次数 NewIPs int `json:"new_ips"` // 今日新IP数量 TotalIPs int `json:"total_ips"` // 总IP数量 } // GetAccessLogStats 获取访问统计数据 func GetAccessLogStats() (*AccessLogStats, error) { stats := &AccessLogStats{} // 今日浏览次数 err := database.DB.QueryRow( "SELECT COUNT(*) FROM access_logs WHERE date(created_at) = date('now', 'localtime')", ).Scan(&stats.TodayViews) if err != nil { return nil, fmt.Errorf("failed to count today views: %w", err) } // 总浏览次数 err = database.DB.QueryRow("SELECT COUNT(*) FROM access_logs").Scan(&stats.TotalViews) if err != nil { return nil, fmt.Errorf("failed to count total views: %w", err) } // 总IP数量 err = database.DB.QueryRow("SELECT COUNT(DISTINCT ip) FROM access_logs").Scan(&stats.TotalIPs) if err != nil { return nil, fmt.Errorf("failed to count total IPs: %w", err) } // 今日新IP数量(今日访问但之前从未出现过的IP) err = database.DB.QueryRow(` SELECT COUNT(DISTINCT a1.ip) FROM access_logs a1 WHERE date(a1.created_at) = date('now', 'localtime') AND NOT EXISTS ( SELECT 1 FROM access_logs a2 WHERE a2.ip = a1.ip AND date(a2.created_at) < date('now', 'localtime') ) `).Scan(&stats.NewIPs) if err != nil { return nil, fmt.Errorf("failed to count new IPs: %w", err) } return stats, nil } // GetRecentAccessLogs 获取最近的访问日志(用于实时流量展示) func GetRecentAccessLogs(limit int) ([]AccessLog, error) { rows, err := database.DB.Query( "SELECT id, ip, user_agent, action_type, detail, referer, created_at FROM access_logs ORDER BY created_at DESC LIMIT ?", limit, ) if err != nil { return nil, fmt.Errorf("failed to query recent access logs: %w", err) } defer rows.Close() var logs []AccessLog for rows.Next() { var l AccessLog if err := rows.Scan(&l.ID, &l.IP, &l.UserAgent, &l.ActionType, &l.Detail, &l.Referer, &l.CreatedAt); err != nil { return nil, fmt.Errorf("failed to scan access log: %w", err) } logs = append(logs, l) } return logs, rows.Err() } // GetAccessLogs 分页获取访问日志,支持按IP和动作类型筛选。 func GetAccessLogs(page, pageSize int, filterIP, filterAction string) ([]AccessLog, int, error) { // 构建查询条件 where := "WHERE 1=1" args := []interface{}{} if filterIP != "" { where += " AND ip LIKE ?" args = append(args, "%"+filterIP+"%") } if filterAction != "" { where += " AND action_type = ?" args = append(args, filterAction) } // 获取总数 var total int countArgs := make([]interface{}, len(args)) copy(countArgs, args) err := database.DB.QueryRow("SELECT COUNT(*) FROM access_logs "+where, countArgs...).Scan(&total) if err != nil { return nil, 0, fmt.Errorf("failed to count access logs: %w", err) } // 分页查询 offset := (page - 1) * pageSize if offset < 0 { offset = 0 } queryArgs := append(args, pageSize, offset) rows, err := database.DB.Query( "SELECT id, ip, user_agent, action_type, detail, referer, created_at FROM access_logs "+where+" ORDER BY created_at DESC LIMIT ? OFFSET ?", queryArgs..., ) if err != nil { return nil, 0, fmt.Errorf("failed to query access logs: %w", err) } defer rows.Close() var logs []AccessLog for rows.Next() { var l AccessLog if err := rows.Scan(&l.ID, &l.IP, &l.UserAgent, &l.ActionType, &l.Detail, &l.Referer, &l.CreatedAt); err != nil { return nil, 0, fmt.Errorf("failed to scan access log: %w", err) } logs = append(logs, l) } return logs, total, rows.Err() } // GetAccessLogStatsByIP 获取按IP统计的访问数据 func GetAccessLogStatsByIP(limit int) ([]struct { IP string `json:"ip"` Visits int `json:"visits"` LastSeen string `json:"last_seen"` }, error) { rows, err := database.DB.Query( "SELECT ip, COUNT(*) as visits, MAX(created_at) as last_seen FROM access_logs GROUP BY ip ORDER BY visits DESC LIMIT ?", limit, ) if err != nil { return nil, fmt.Errorf("failed to query IP stats: %w", err) } defer rows.Close() var result []struct { IP string `json:"ip"` Visits int `json:"visits"` LastSeen string `json:"last_seen"` } for rows.Next() { var r struct { IP string `json:"ip"` Visits int `json:"visits"` LastSeen string `json:"last_seen"` } if err := rows.Scan(&r.IP, &r.Visits, &r.LastSeen); err != nil { return nil, fmt.Errorf("failed to scan IP stats: %w", err) } result = append(result, r) } return result, rows.Err() }