From fdf0c1442c0cdfb51f8aaae73eb4c6e4f3c71bc0 Mon Sep 17 00:00:00 2001 From: nanako <469449812@qq.com> Date: Tue, 11 Nov 2025 15:25:16 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8C=81=E4=B9=85=E5=8C=96=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E5=9B=BA=E5=AE=9A=E8=B7=AF=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/api/handlers.go | 129 +++++++++- backend/internal/config/config.go | 168 +++++++++++++ backend/internal/db/database.go | 10 +- backend/internal/scheduler/log_cleaner.go | 279 ++++++++++++++++++++++ backend/main.go | 68 +++++- config.example.json | 18 ++ 6 files changed, 642 insertions(+), 30 deletions(-) create mode 100644 backend/internal/config/config.go create mode 100644 backend/internal/scheduler/log_cleaner.go create mode 100644 config.example.json diff --git a/backend/api/handlers.go b/backend/api/handlers.go index 883740e..9261ee8 100644 --- a/backend/api/handlers.go +++ b/backend/api/handlers.go @@ -3,6 +3,7 @@ package api import ( "ai-gateway/internal/billing" "ai-gateway/internal/models" + "ai-gateway/internal/scheduler" "bytes" "encoding/json" "fmt" @@ -15,7 +16,8 @@ import ( // APIHandler 持有数据库连接并处理API请求 type APIHandler struct { - DB *gorm.DB + DB *gorm.DB + LogCleaner *scheduler.LogCleaner } // RequestLogListItem 精简的请求日志列表项(不包含RequestBody和ResponseBody) @@ -398,6 +400,14 @@ func (h *APIHandler) ClearRequestLogsHandler(c *gin.Context) { // 获取查询参数 olderThan := c.Query("older_than") // 清空多少天前的日志 + // 先查询当前总记录数(包括软删除的记录) + var totalCountBefore int64 + h.DB.Unscoped().Model(&models.RequestLog{}).Count(&totalCountBefore) + + // 查询活跃记录数(不包括软删除的记录) + var activeCountBefore int64 + h.DB.Model(&models.RequestLog{}).Count(&activeCountBefore) + var startTime time.Time if olderThan != "" { // 解析天数 @@ -414,19 +424,40 @@ func (h *APIHandler) ClearRequestLogsHandler(c *gin.Context) { // 如果是0天,要删除所有日志,不需要时间限制 if days == 0 { - // 执行删除所有日志的操作 - result := h.DB.Where("1 = 1").Delete(&models.RequestLog{}) + // 使用Unscoped进行硬删除,真正删除记录而非软删除 + result := h.DB.Unscoped().Where("1 = 1").Delete(&models.RequestLog{}) if result.Error != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to clear logs"}) return } - // 返回删除的记录数 - c.JSON(http.StatusOK, gin.H{ - "message": "All logs cleared successfully", + // 执行VACUUM来回收数据库空间 + vacuumError := "" + if err := h.DB.Exec("VACUUM").Error; err != nil { + vacuumError = fmt.Sprintf("VACUUM failed: %v", err) + fmt.Printf("Warning: %s\n", vacuumError) + } + + // 查询删除后的记录数 + var totalCountAfter int64 + h.DB.Unscoped().Model(&models.RequestLog{}).Count(&totalCountAfter) + + // 返回详细的删除信息 + response := gin.H{ + "message": "All logs cleared successfully with hard delete and vacuum", "deleted_count": result.RowsAffected, "older_than_days": olderThan, - }) + "total_count_before": totalCountBefore, + "active_count_before": activeCountBefore, + "total_count_after": totalCountAfter, + "hard_delete": true, + "vacuum_executed": vacuumError == "", + } + if vacuumError != "" { + response["vacuum_error"] = vacuumError + } + + c.JSON(http.StatusOK, response) return } @@ -436,17 +467,91 @@ func (h *APIHandler) ClearRequestLogsHandler(c *gin.Context) { startTime = time.Now().AddDate(0, 0, -30) } - // 执行删除操作 - result := h.DB.Where("created_at < ?", startTime).Delete(&models.RequestLog{}) + // 使用Unscoped执行硬删除操作,真正删除记录而非软删除 + result := h.DB.Unscoped().Where("created_at < ?", startTime).Delete(&models.RequestLog{}) if result.Error != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to clear logs"}) return } - // 返回删除的记录数 - c.JSON(http.StatusOK, gin.H{ - "message": "Logs cleared successfully", + // 执行VACUUM来回收数据库空间 + vacuumError := "" + if err := h.DB.Exec("VACUUM").Error; err != nil { + vacuumError = fmt.Sprintf("VACUUM failed: %v", err) + fmt.Printf("Warning: %s\n", vacuumError) + } + + // 查询删除后的记录数 + var totalCountAfter int64 + h.DB.Unscoped().Model(&models.RequestLog{}).Count(&totalCountAfter) + + // 返回详细的删除信息 + response := gin.H{ + "message": "Logs cleared successfully with hard delete and vacuum", "deleted_count": result.RowsAffected, "older_than_days": olderThan, + "cutoff_time": startTime, + "total_count_before": totalCountBefore, + "active_count_before": activeCountBefore, + "total_count_after": totalCountAfter, + "hard_delete": true, + "vacuum_executed": vacuumError == "", + } + if vacuumError != "" { + response["vacuum_error"] = vacuumError + } + + c.JSON(http.StatusOK, response) +} + + +// GetLogCleanerStatusHandler 获取日志清理器状态 +func (h *APIHandler) GetLogCleanerStatusHandler(c *gin.Context) { + if h.LogCleaner == nil { + c.JSON(http.StatusOK, gin.H{ + "enabled": false, + "message": "Log cleaner is not initialized", + }) + return + } + + config := h.LogCleaner.GetConfig() + nextExecuteTime := h.LogCleaner.GetNextExecuteTime() + + c.JSON(http.StatusOK, gin.H{ + "enabled": config.Enabled, + "execute_time": config.ExecuteTime, + "retention_days": config.RetentionDays, + "check_interval": config.CheckInterval, + "next_execute_time": nextExecuteTime.Format("2006-01-02 15:04:05"), + "time_until_next": time.Until(nextExecuteTime).String(), + }) +} + +// ForceLogCleanupHandler 手动触发日志清理 +func (h *APIHandler) ForceLogCleanupHandler(c *gin.Context) { + if h.LogCleaner == nil { + c.JSON(http.StatusServiceUnavailable, gin.H{ + "error": "Log cleaner is not initialized", + }) + return + } + + // 执行清理 + report := h.LogCleaner.ForceCleanup() + + // 返回清理报告 + c.JSON(http.StatusOK, gin.H{ + "message": "Manual log cleanup completed", + "execute_time": report.ExecuteTime.Format("2006-01-02 15:04:05"), + "duration": report.Duration.String(), + "deleted_count": report.DeletedCount, + "total_count_before": report.TotalCountBefore, + "active_count_before": report.ActiveCountBefore, + "total_count_after": report.TotalCountAfter, + "cutoff_time": report.CutoffTime.Format("2006-01-02 15:04:05"), + "vacuum_duration": report.VacuumDuration.String(), + "vacuum_error": report.VacuumError, + "success": report.Success, }) } diff --git a/backend/internal/config/config.go b/backend/internal/config/config.go new file mode 100644 index 0000000..c08a15a --- /dev/null +++ b/backend/internal/config/config.go @@ -0,0 +1,168 @@ +package config + +import ( + "ai-gateway/internal/scheduler" + "encoding/json" + "fmt" + "io/ioutil" + "log" + "os" +) + +// 固定路径常量 - 用于 Docker 挂载 +const ( + // DataDir 数据目录,用于存放配置和数据库文件(固定路径,便于 Docker 挂载) + DataDir = "/data" + // ConfigPath 配置文件固定路径 + ConfigPath = "/data/config.json" + // DatabasePath 数据库文件固定路径 + DatabasePath = "/data/gateway.db" +) + +// Config 应用程序配置结构 +type Config struct { + // 服务器配置 + Server ServerConfig `json:"server"` + // 日志清理配置 + LogCleaner scheduler.LogCleanerConfig `json:"log_cleaner"` + // 应用配置 + App AppConfig `json:"app"` +} + +// ServerConfig 服务器配置 +type ServerConfig struct { + Port string `json:"port"` + Host string `json:"host"` +} + +// AppConfig 应用配置 +type AppConfig struct { + Name string `json:"name"` + Version string `json:"version"` + Environment string `json:"environment"` + LogLevel string `json:"log_level"` +} + +// DefaultConfig 默认配置 +func DefaultConfig() *Config { + return &Config{ + Server: ServerConfig{ + Port: "8080", + Host: "0.0.0.0", + }, + LogCleaner: scheduler.LogCleanerConfig{ + Enabled: true, // 默认启用自动清理 + ExecuteTime: "02:00", // 凌晨2点执行 + RetentionDays: 7, // 保留7天的日志 + CheckInterval: 5, // 每5分钟检查一次 + }, + App: AppConfig{ + Name: "AI Gateway", + Version: "1.0.0", + Environment: "production", + LogLevel: "info", + }, + } +} + +// LoadConfig 从文件加载配置 +func LoadConfig(filepath string) (*Config, error) { + // 确保数据目录存在 + if err := os.MkdirAll(DataDir, 0755); err != nil { + log.Printf("⚠️ 创建数据目录失败: %v", err) + } + // 如果配置文件不存在,返回默认配置并创建配置文件 + if _, err := os.Stat(filepath); os.IsNotExist(err) { + log.Printf("⚠️ 配置文件不存在,使用默认配置并创建: %s", filepath) + config := DefaultConfig() + if err := config.Save(filepath); err != nil { + log.Printf("❌ 保存默认配置失败: %v", err) + // 即使保存失败,仍然返回默认配置 + return config, nil + } + log.Printf("✅ 已创建默认配置文件: %s", filepath) + return config, nil + } + + // 读取配置文件 + data, err := ioutil.ReadFile(filepath) + if err != nil { + return nil, fmt.Errorf("读取配置文件失败: %v", err) + } + + // 解析JSON配置 + var config Config + if err := json.Unmarshal(data, &config); err != nil { + return nil, fmt.Errorf("解析配置文件失败: %v", err) + } + + log.Printf("✅ 配置文件加载成功: %s", filepath) + return &config, nil +} + +// LoadConfigOrDefault 加载配置或使用默认配置(不会失败) +func LoadConfigOrDefault(filepath string) *Config { + config, err := LoadConfig(filepath) + if err != nil { + log.Printf("⚠️ 加载配置失败,使用默认配置: %v", err) + return DefaultConfig() + } + return config +} + +// Save 保存配置到文件 +func (c *Config) Save(filepath string) error { + // 将配置序列化为格式化的JSON + data, err := json.MarshalIndent(c, "", " ") + if err != nil { + return fmt.Errorf("序列化配置失败: %v", err) + } + + // 写入文件 + if err := ioutil.WriteFile(filepath, data, 0644); err != nil { + return fmt.Errorf("写入配置文件失败: %v", err) + } + + return nil +} + +// Validate 验证配置的有效性 +func (c *Config) Validate() error { + // 验证服务器端口 + if c.Server.Port == "" { + return fmt.Errorf("服务器端口不能为空") + } + + // 验证日志清理配置 + if c.LogCleaner.Enabled { + if c.LogCleaner.ExecuteTime == "" { + return fmt.Errorf("日志清理执行时间不能为空") + } + if c.LogCleaner.RetentionDays < 0 { + return fmt.Errorf("日志保留天数不能为负数") + } + if c.LogCleaner.CheckInterval <= 0 { + c.LogCleaner.CheckInterval = 5 // 设置默认值 + } + } + + return nil +} + +// Print 打印配置信息(用于调试) +func (c *Config) Print() { + log.Println("📋 当前配置:") + log.Printf(" 📁 数据目录: %s", DataDir) + log.Printf(" 📄 配置文件: %s", ConfigPath) + log.Printf(" 🗄️ 数据库路径: %s (固定)", DatabasePath) + log.Printf(" 🌐 服务器地址: %s:%s", c.Server.Host, c.Server.Port) + log.Printf(" 🧹 日志自动清理: %v", c.LogCleaner.Enabled) + if c.LogCleaner.Enabled { + log.Printf(" ⏰ 执行时间: %s", c.LogCleaner.ExecuteTime) + log.Printf(" 📅 保留天数: %d天", c.LogCleaner.RetentionDays) + log.Printf(" 🔍 检查间隔: %d分钟", c.LogCleaner.CheckInterval) + } + log.Printf(" 📱 应用名称: %s v%s", c.App.Name, c.App.Version) + log.Printf(" 🌍 运行环境: %s", c.App.Environment) + log.Printf(" 📝 日志级别: %s", c.App.LogLevel) +} \ No newline at end of file diff --git a/backend/internal/db/database.go b/backend/internal/db/database.go index 83ff3cc..f065bd0 100644 --- a/backend/internal/db/database.go +++ b/backend/internal/db/database.go @@ -3,20 +3,14 @@ package db import ( "ai-gateway/internal/models" "log" - "os" "gorm.io/driver/sqlite" "gorm.io/gorm" ) // InitDB 初始化数据库连接并执行自动迁移 -func InitDB() (*gorm.DB, error) { - // 从环境变量读取数据库路径,如果未设置则使用默认路径 - dbPath := os.Getenv("DB_PATH") - if dbPath == "" { - dbPath = "gateway.db" - } - +// 参数 dbPath: 数据库文件路径 +func InitDB(dbPath string) (*gorm.DB, error) { // 使用SQLite驱动连接到数据库文件 db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{}) if err != nil { diff --git a/backend/internal/scheduler/log_cleaner.go b/backend/internal/scheduler/log_cleaner.go new file mode 100644 index 0000000..ce8f50e --- /dev/null +++ b/backend/internal/scheduler/log_cleaner.go @@ -0,0 +1,279 @@ +package scheduler + +import ( + "ai-gateway/internal/models" + "fmt" + "log" + "time" + + "gorm.io/gorm" +) + +// LogCleanerConfig 日志清理器配置 +type LogCleanerConfig struct { + // 启用状态 + Enabled bool `json:"enabled"` + // 执行时间(24小时制,如:"02:00" 表示凌晨2点) + ExecuteTime string `json:"execute_time"` + // 保留天数(清理多少天之前的日志) + RetentionDays int `json:"retention_days"` + // 检查间隔(分钟) + CheckInterval int `json:"check_interval"` +} + +// LogCleaner 日志清理器 +type LogCleaner struct { + db *gorm.DB + config LogCleanerConfig + ticker *time.Ticker + quit chan struct{} +} + +// NewLogCleaner 创建日志清理器实例 +func NewLogCleaner(db *gorm.DB, config LogCleanerConfig) *LogCleaner { + return &LogCleaner{ + db: db, + config: config, + quit: make(chan struct{}), + } +} + +// Start 启动定时清理任务 +func (lc *LogCleaner) Start() { + if !lc.config.Enabled { + log.Println("📅 日志自动清理功能已禁用") + return + } + + log.Printf("🚀 启动日志自动清理器 - 执行时间: %s, 保留天数: %d天", + lc.config.ExecuteTime, lc.config.RetentionDays) + + // 设置检查间隔,默认5分钟检查一次 + checkInterval := time.Duration(lc.config.CheckInterval) * time.Minute + if checkInterval == 0 { + checkInterval = 5 * time.Minute + } + + lc.ticker = time.NewTicker(checkInterval) + + go func() { + // 启动时先检查一次是否需要立即执行 + lc.checkAndExecuteCleanup() + + for { + select { + case <-lc.ticker.C: + lc.checkAndExecuteCleanup() + case <-lc.quit: + lc.ticker.Stop() + log.Println("🛑 日志自动清理器已停止") + return + } + } + }() +} + +// Stop 停止定时清理任务 +func (lc *LogCleaner) Stop() { + if lc.ticker != nil { + close(lc.quit) + } +} + +// checkAndExecuteCleanup 检查并执行清理任务 +func (lc *LogCleaner) checkAndExecuteCleanup() { + now := time.Now() + + // 解析配置的执行时间 + targetTime, err := time.Parse("15:04", lc.config.ExecuteTime) + if err != nil { + log.Printf("❌ 执行时间格式错误: %v", err) + return + } + + // 构造今天的目标执行时间 + todayTarget := time.Date(now.Year(), now.Month(), now.Day(), + targetTime.Hour(), targetTime.Minute(), 0, 0, now.Location()) + + // 如果当前时间已经超过今天的目标时间,则计算明天的目标时间 + if now.After(todayTarget) { + todayTarget = todayTarget.Add(24 * time.Hour) + } + + // 检查是否到了执行时间(允许5分钟的误差) + timeDiff := todayTarget.Sub(now) + if timeDiff > 0 && timeDiff <= 5*time.Minute { + log.Printf("⏰ 开始执行定时日志清理任务 - 当前时间: %s", now.Format("2006-01-02 15:04:05")) + lc.executeCleanup() + } +} + +// executeCleanup 执行日志清理 +func (lc *LogCleaner) executeCleanup() { + startTime := time.Now() + + // 计算清理的截止时间 + cutoffTime := startTime.AddDate(0, 0, -lc.config.RetentionDays) + + log.Printf("🧹 开始自动清理日志 - 删除 %s 之前的日志", cutoffTime.Format("2006-01-02 15:04:05")) + + // 查询删除前的统计信息 + var totalCountBefore, activeCountBefore int64 + lc.db.Unscoped().Model(&models.RequestLog{}).Count(&totalCountBefore) + lc.db.Model(&models.RequestLog{}).Count(&activeCountBefore) + + // 执行硬删除 + result := lc.db.Unscoped().Where("created_at < ?", cutoffTime).Delete(&models.RequestLog{}) + if result.Error != nil { + log.Printf("❌ 自动清理日志失败: %v", result.Error) + return + } + + // 执行VACUUM操作回收空间 + vacuumStart := time.Now() + vacuumError := "" + if err := lc.db.Exec("VACUUM").Error; err != nil { + vacuumError = fmt.Sprintf("VACUUM failed: %v", err) + log.Printf("⚠️ 警告: %s", vacuumError) + } + vacuumDuration := time.Since(vacuumStart) + + // 查询删除后的统计信息 + var totalCountAfter int64 + lc.db.Unscoped().Model(&models.RequestLog{}).Count(&totalCountAfter) + + // 计算执行时间 + duration := time.Since(startTime) + + // 记录清理结果 + log.Printf("✅ 自动日志清理完成:") + log.Printf(" 📊 删除记录数: %d", result.RowsAffected) + log.Printf(" 📈 删除前总数: %d (活跃: %d)", totalCountBefore, activeCountBefore) + log.Printf(" 📉 删除后总数: %d", totalCountAfter) + log.Printf(" 🗜️ VACUUM耗时: %v", vacuumDuration) + log.Printf(" ⏱️ 总耗时: %v", duration) + log.Printf(" 📅 清理截止时间: %s", cutoffTime.Format("2006-01-02 15:04:05")) + + if vacuumError != "" { + log.Printf(" ⚠️ VACUUM警告: %s", vacuumError) + } + + // 可选:发送清理报告到监控系统或通知系统 + lc.sendCleanupReport(CleanupReport{ + ExecuteTime: startTime, + Duration: duration, + DeletedCount: result.RowsAffected, + TotalCountBefore: totalCountBefore, + ActiveCountBefore: activeCountBefore, + TotalCountAfter: totalCountAfter, + CutoffTime: cutoffTime, + VacuumDuration: vacuumDuration, + VacuumError: vacuumError, + Success: result.Error == nil, + }) +} + +// CleanupReport 清理报告结构 +type CleanupReport struct { + ExecuteTime time.Time `json:"execute_time"` + Duration time.Duration `json:"duration"` + DeletedCount int64 `json:"deleted_count"` + TotalCountBefore int64 `json:"total_count_before"` + ActiveCountBefore int64 `json:"active_count_before"` + TotalCountAfter int64 `json:"total_count_after"` + CutoffTime time.Time `json:"cutoff_time"` + VacuumDuration time.Duration `json:"vacuum_duration"` + VacuumError string `json:"vacuum_error,omitempty"` + Success bool `json:"success"` +} + +// sendCleanupReport 发送清理报告(可扩展为发送到监控系统) +func (lc *LogCleaner) sendCleanupReport(report CleanupReport) { + // 这里可以扩展为发送报告到: + // 1. 监控系统(如Prometheus) + // 2. 通知系统(如邮件、企业微信等) + // 3. 日志聚合系统(如ELK) + // 4. 数据库记录清理历史 + + // 目前只记录到应用日志 + if report.Success { + log.Printf("📋 清理报告已生成 - 删除 %d 条记录,耗时 %v", + report.DeletedCount, report.Duration) + } else { + log.Printf("📋 清理报告已生成 - 执行失败,耗时 %v", report.Duration) + } +} + +// GetConfig 获取当前配置 +func (lc *LogCleaner) GetConfig() LogCleanerConfig { + return lc.config +} + +// UpdateConfig 更新配置(运行时动态调整) +func (lc *LogCleaner) UpdateConfig(newConfig LogCleanerConfig) { + lc.config = newConfig + log.Printf("🔄 日志清理器配置已更新 - 启用: %v, 执行时间: %s, 保留天数: %d", + newConfig.Enabled, newConfig.ExecuteTime, newConfig.RetentionDays) +} + +// GetNextExecuteTime 获取下次执行时间 +func (lc *LogCleaner) GetNextExecuteTime() time.Time { + now := time.Now() + targetTime, err := time.Parse("15:04", lc.config.ExecuteTime) + if err != nil { + return time.Time{} + } + + // 构造今天的目标执行时间 + todayTarget := time.Date(now.Year(), now.Month(), now.Day(), + targetTime.Hour(), targetTime.Minute(), 0, 0, now.Location()) + + // 如果当前时间已经超过今天的目标时间,则返回明天的目标时间 + if now.After(todayTarget) { + return todayTarget.Add(24 * time.Hour) + } + + return todayTarget +} + +// ForceCleanup 手动触发清理(用于测试或紧急清理) +func (lc *LogCleaner) ForceCleanup() CleanupReport { + log.Printf("🔧 手动触发日志清理") + + startTime := time.Now() + cutoffTime := startTime.AddDate(0, 0, -lc.config.RetentionDays) + + var totalCountBefore, activeCountBefore int64 + lc.db.Unscoped().Model(&models.RequestLog{}).Count(&totalCountBefore) + lc.db.Model(&models.RequestLog{}).Count(&activeCountBefore) + + result := lc.db.Unscoped().Where("created_at < ?", cutoffTime).Delete(&models.RequestLog{}) + + vacuumStart := time.Now() + vacuumError := "" + if err := lc.db.Exec("VACUUM").Error; err != nil { + vacuumError = fmt.Sprintf("VACUUM failed: %v", err) + } + vacuumDuration := time.Since(vacuumStart) + + var totalCountAfter int64 + lc.db.Unscoped().Model(&models.RequestLog{}).Count(&totalCountAfter) + + duration := time.Since(startTime) + + report := CleanupReport{ + ExecuteTime: startTime, + Duration: duration, + DeletedCount: result.RowsAffected, + TotalCountBefore: totalCountBefore, + ActiveCountBefore: activeCountBefore, + TotalCountAfter: totalCountAfter, + CutoffTime: cutoffTime, + VacuumDuration: vacuumDuration, + VacuumError: vacuumError, + Success: result.Error == nil, + } + + lc.sendCleanupReport(report) + return report +} \ No newline at end of file diff --git a/backend/main.go b/backend/main.go index 092b856..4110999 100644 --- a/backend/main.go +++ b/backend/main.go @@ -2,21 +2,51 @@ package main import ( "ai-gateway/api" + "ai-gateway/internal/config" "ai-gateway/internal/db" "ai-gateway/internal/middleware" + "ai-gateway/internal/scheduler" "log" + "os" + "os/signal" + "syscall" "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" ) func main() { - // 初始化数据库连接 - database, err := db.InitDB() - if err != nil { - log.Fatalf("Failed to initialize database: %v", err) + log.Println("🚀 启动 AI Gateway 服务...") + + // 加载配置文件(使用固定路径,便于 Docker 挂载) + cfg := config.LoadConfigOrDefault(config.ConfigPath) + cfg.Print() + + // 验证配置 + if err := cfg.Validate(); err != nil { + log.Printf("⚠️ 配置验证失败: %v,使用默认配置", err) + cfg = config.DefaultConfig() } + // 初始化数据库连接(使用固定数据库路径) + database, err := db.InitDB(config.DatabasePath) + if err != nil { + log.Fatalf("❌ 数据库初始化失败: %v", err) + } + log.Printf("✅ 数据库连接成功: %s", config.DatabasePath) + + // 初始化并启动日志清理器 + logCleaner := scheduler.NewLogCleaner(database, cfg.LogCleaner) + logCleaner.Start() + log.Println("✅ 日志自动清理器已启动") + + // 设置优雅关闭 + defer func() { + log.Println("🛑 正在关闭服务...") + logCleaner.Stop() + log.Println("✅ 日志自动清理器已停止") + }() + // 创建Gin路由器实例 router := gin.Default() @@ -36,7 +66,8 @@ func main() { // 创建API处理器 handler := &api.APIHandler{ - DB: database, + DB: database, + LogCleaner: logCleaner, } // 添加健康检查端点(无需认证) @@ -83,11 +114,28 @@ func main() { api_.GET("/logs/stats", handler.GetRequestLogStatsHandler) api_.GET("/logs/:id", handler.GetRequestLogDetailHandler) api_.DELETE("/logs", handler.ClearRequestLogsHandler) + + // Log Cleaner Management + api_.GET("/log-cleaner/status", handler.GetLogCleanerStatusHandler) + api_.POST("/log-cleaner/force-cleanup", handler.ForceLogCleanupHandler) } - // 启动HTTP服务器 - log.Println("Starting AI Gateway server on :8080") - if err := router.Run(":8080"); err != nil { - log.Fatalf("Failed to start server: %v", err) - } + // 设置优雅关闭信号处理 + quit := make(chan os.Signal, 1) + signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) + + // 在goroutine中启动HTTP服务器 + go func() { + serverAddr := cfg.Server.Host + ":" + cfg.Server.Port + log.Printf("🌐 HTTP服务器启动在 %s", serverAddr) + if err := router.Run(serverAddr); err != nil { + log.Fatalf("❌ HTTP服务器启动失败: %v", err) + } + }() + + // 等待退出信号 + <-quit + log.Println("🛑 收到退出信号,正在优雅关闭...") + logCleaner.Stop() + log.Println("✅ AI Gateway 服务已关闭") } diff --git a/config.example.json b/config.example.json new file mode 100644 index 0000000..bc58960 --- /dev/null +++ b/config.example.json @@ -0,0 +1,18 @@ +{ + "server": { + "port": "8080", + "host": "0.0.0.0" + }, + "log_cleaner": { + "enabled": true, + "execute_time": "02:00", + "retention_days": 7, + "check_interval": 5 + }, + "app": { + "name": "AI Gateway", + "version": "1.0.0", + "environment": "production", + "log_level": "info" + } +} \ No newline at end of file