251 lines
5.8 KiB
Go
251 lines
5.8 KiB
Go
package api
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// RequestLog 请求日志
|
|
type RequestLog struct {
|
|
ID int `json:"id"` // 序号
|
|
Method string `json:"method"` // 请求方法
|
|
URL string `json:"url"` // 请求地址
|
|
Host string `json:"host"` // 主机地址
|
|
Path string `json:"path"` // 请求路径
|
|
Headers map[string]string `json:"headers"` // 请求头
|
|
Cookies map[string]string `json:"cookies"` // Cookie
|
|
BodyType string `json:"bodyType"` // 数据类型
|
|
Body string `json:"body"` // 请求体
|
|
StatusCode int `json:"statusCode"` // 状态码
|
|
Status string `json:"status"` // 状态描述
|
|
RespHeaders map[string]string `json:"respHeaders"` // 响应头
|
|
RespBody string `json:"respBody"` // 响应正文
|
|
RespSize int64 `json:"respSize"` // 响应大小
|
|
Time string `json:"time"` // 请求耗时
|
|
CreatedAt string `json:"createdAt"` // 创建时间
|
|
}
|
|
|
|
// LogManager 日志管理器
|
|
type LogManager struct {
|
|
mu sync.RWMutex
|
|
logDir string
|
|
logs []*RequestLog
|
|
maxLogs int
|
|
fileName string
|
|
}
|
|
|
|
var (
|
|
logManager *LogManager
|
|
logManagerOnce sync.Once
|
|
)
|
|
|
|
// InitLogManager 初始化日志管理器
|
|
func InitLogManager(logDir string) {
|
|
logManager = &LogManager{
|
|
logDir: logDir,
|
|
logs: make([]*RequestLog, 0),
|
|
maxLogs: 1000,
|
|
fileName: "http_requests.json",
|
|
}
|
|
logManager.loadLogs()
|
|
}
|
|
|
|
// GetLogManager 获取日志管理器(并发安全的单例)
|
|
func GetLogManager() *LogManager {
|
|
logManagerOnce.Do(func() {
|
|
if logManager == nil {
|
|
InitLogManager("logs")
|
|
}
|
|
})
|
|
return logManager
|
|
}
|
|
|
|
// loadLogs 从文件加载日志
|
|
func (m *LogManager) loadLogs() {
|
|
filePath := filepath.Join(m.logDir, m.fileName)
|
|
data, err := os.ReadFile(filePath)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
m.logs = make([]*RequestLog, 0)
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
var logs []*RequestLog
|
|
if err := json.Unmarshal(data, &logs); err != nil {
|
|
m.logs = make([]*RequestLog, 0)
|
|
return
|
|
}
|
|
|
|
m.logs = logs
|
|
}
|
|
|
|
// saveLogs 保存日志到文件
|
|
func (m *LogManager) saveLogs() error {
|
|
if err := os.MkdirAll(m.logDir, 0755); err != nil {
|
|
return err
|
|
}
|
|
|
|
filePath := filepath.Join(m.logDir, m.fileName)
|
|
data, err := json.MarshalIndent(m.logs, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return os.WriteFile(filePath, data, 0644)
|
|
}
|
|
|
|
// AddLog 添加日志
|
|
func (m *LogManager) AddLog(log *RequestLog) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
// 生成序号
|
|
if len(m.logs) > 0 {
|
|
log.ID = m.logs[0].ID + 1
|
|
} else {
|
|
log.ID = 1
|
|
}
|
|
|
|
log.CreatedAt = time.Now().Format(time.DateTime)
|
|
|
|
// 插入到开头(最新的在前面)
|
|
m.logs = append([]*RequestLog{log}, m.logs...)
|
|
|
|
// 限制最大日志数量
|
|
if len(m.logs) > m.maxLogs {
|
|
m.logs = m.logs[:m.maxLogs]
|
|
}
|
|
|
|
return m.saveLogs()
|
|
}
|
|
|
|
// GetLogs 获取所有日志
|
|
func (m *LogManager) GetLogs() []*RequestLog {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
return m.logs
|
|
}
|
|
|
|
// GetLog 获取单条日志
|
|
func (m *LogManager) GetLog(id int) *RequestLog {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
for _, log := range m.logs {
|
|
if log.ID == id {
|
|
return log
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// DeleteLog 删除日志
|
|
func (m *LogManager) DeleteLog(id int) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
for i, log := range m.logs {
|
|
if log.ID == id {
|
|
m.logs = append(m.logs[:i], m.logs[i+1:]...)
|
|
return m.saveLogs()
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ClearLogs 清空日志
|
|
func (m *LogManager) ClearLogs() error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.logs = make([]*RequestLog, 0)
|
|
return m.saveLogs()
|
|
}
|
|
|
|
// SearchLogs 搜索日志
|
|
func (m *LogManager) SearchLogs(keyword string) []*RequestLog {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
var result []*RequestLog
|
|
keyword = strings.ToLower(keyword)
|
|
|
|
for _, log := range m.logs {
|
|
if strings.Contains(strings.ToLower(log.URL), keyword) ||
|
|
strings.Contains(strings.ToLower(log.Method), keyword) ||
|
|
strings.Contains(strings.ToLower(log.Host), keyword) ||
|
|
strings.Contains(strings.ToLower(log.Path), keyword) {
|
|
result = append(result, log)
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// CreateLogFromResponse 从响应创建日志
|
|
func CreateLogFromResponse(req *HttpRequest, resp *HttpResponse) *RequestLog {
|
|
// 解析 URL
|
|
host := ""
|
|
path := req.URL
|
|
for _, prefix := range []string{"http://", "https://"} {
|
|
if strings.HasPrefix(req.URL, prefix) {
|
|
parts := strings.SplitN(strings.TrimPrefix(req.URL, prefix), "/", 2)
|
|
host = parts[0]
|
|
if len(parts) > 1 {
|
|
path = "/" + parts[1]
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
return &RequestLog{
|
|
Method: req.Method,
|
|
URL: req.URL,
|
|
Host: host,
|
|
Path: path,
|
|
Headers: req.Headers,
|
|
Cookies: req.Cookies,
|
|
BodyType: req.BodyType,
|
|
Body: req.Body,
|
|
StatusCode: resp.StatusCode,
|
|
Status: resp.Status,
|
|
RespHeaders: resp.Headers,
|
|
RespBody: resp.Body,
|
|
RespSize: resp.Size,
|
|
Time: resp.Time,
|
|
}
|
|
}
|
|
|
|
// FormatFileSize 格式化文件大小
|
|
func FormatFileSize(size int64) string {
|
|
if size < 1024 {
|
|
return fmt.Sprintf("%d B", size)
|
|
} else if size < 1024*1024 {
|
|
return fmt.Sprintf("%.2f KB", float64(size)/1024)
|
|
} else if size < 1024*1024*1024 {
|
|
return fmt.Sprintf("%.2f MB", float64(size)/(1024*1024))
|
|
}
|
|
return fmt.Sprintf("%.2f GB", float64(size)/(1024*1024*1024))
|
|
}
|
|
|
|
// SortLogsByTime 按时间排序日志
|
|
func SortLogsByTime(logs []*RequestLog, desc bool) []*RequestLog {
|
|
sorted := make([]*RequestLog, len(logs))
|
|
copy(sorted, logs)
|
|
|
|
sort.Slice(sorted, func(i, j int) bool {
|
|
timeI, _ := time.Parse(time.DateTime, sorted[i].CreatedAt)
|
|
timeJ, _ := time.Parse(time.DateTime, sorted[j].CreatedAt)
|
|
if desc {
|
|
return timeI.After(timeJ)
|
|
}
|
|
return timeI.Before(timeJ)
|
|
})
|
|
|
|
return sorted
|
|
}
|