持久化数据使用固定路径

This commit is contained in:
2025-11-11 15:25:16 +08:00
parent 1ae955be73
commit fdf0c1442c
6 changed files with 642 additions and 30 deletions

View File

@@ -3,6 +3,7 @@ package api
import ( import (
"ai-gateway/internal/billing" "ai-gateway/internal/billing"
"ai-gateway/internal/models" "ai-gateway/internal/models"
"ai-gateway/internal/scheduler"
"bytes" "bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
@@ -15,7 +16,8 @@ import (
// APIHandler 持有数据库连接并处理API请求 // APIHandler 持有数据库连接并处理API请求
type APIHandler struct { type APIHandler struct {
DB *gorm.DB DB *gorm.DB
LogCleaner *scheduler.LogCleaner
} }
// RequestLogListItem 精简的请求日志列表项不包含RequestBody和ResponseBody // RequestLogListItem 精简的请求日志列表项不包含RequestBody和ResponseBody
@@ -398,6 +400,14 @@ func (h *APIHandler) ClearRequestLogsHandler(c *gin.Context) {
// 获取查询参数 // 获取查询参数
olderThan := c.Query("older_than") // 清空多少天前的日志 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 var startTime time.Time
if olderThan != "" { if olderThan != "" {
// 解析天数 // 解析天数
@@ -414,19 +424,40 @@ func (h *APIHandler) ClearRequestLogsHandler(c *gin.Context) {
// 如果是0天要删除所有日志不需要时间限制 // 如果是0天要删除所有日志不需要时间限制
if days == 0 { if days == 0 {
// 执行删除所有日志的操作 // 使用Unscoped进行硬删除真正删除记录而非软删除
result := h.DB.Where("1 = 1").Delete(&models.RequestLog{}) result := h.DB.Unscoped().Where("1 = 1").Delete(&models.RequestLog{})
if result.Error != nil { if result.Error != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to clear logs"}) c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to clear logs"})
return return
} }
// 返回删除的记录数 // 执行VACUUM来回收数据库空间
c.JSON(http.StatusOK, gin.H{ vacuumError := ""
"message": "All logs cleared successfully", 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, "deleted_count": result.RowsAffected,
"older_than_days": olderThan, "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 return
} }
@@ -436,17 +467,91 @@ func (h *APIHandler) ClearRequestLogsHandler(c *gin.Context) {
startTime = time.Now().AddDate(0, 0, -30) startTime = time.Now().AddDate(0, 0, -30)
} }
// 执行删除操作 // 使用Unscoped执行删除操作,真正删除记录而非软删除
result := h.DB.Where("created_at < ?", startTime).Delete(&models.RequestLog{}) result := h.DB.Unscoped().Where("created_at < ?", startTime).Delete(&models.RequestLog{})
if result.Error != nil { if result.Error != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to clear logs"}) c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to clear logs"})
return return
} }
// 返回删除的记录数 // 执行VACUUM来回收数据库空间
c.JSON(http.StatusOK, gin.H{ vacuumError := ""
"message": "Logs cleared successfully", 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, "deleted_count": result.RowsAffected,
"older_than_days": olderThan, "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,
}) })
} }

View File

@@ -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)
}

View File

@@ -3,20 +3,14 @@ package db
import ( import (
"ai-gateway/internal/models" "ai-gateway/internal/models"
"log" "log"
"os"
"gorm.io/driver/sqlite" "gorm.io/driver/sqlite"
"gorm.io/gorm" "gorm.io/gorm"
) )
// InitDB 初始化数据库连接并执行自动迁移 // InitDB 初始化数据库连接并执行自动迁移
func InitDB() (*gorm.DB, error) { // 参数 dbPath: 数据库文件路径
// 从环境变量读取数据库路径,如果未设置则使用默认路径 func InitDB(dbPath string) (*gorm.DB, error) {
dbPath := os.Getenv("DB_PATH")
if dbPath == "" {
dbPath = "gateway.db"
}
// 使用SQLite驱动连接到数据库文件 // 使用SQLite驱动连接到数据库文件
db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{}) db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{})
if err != nil { if err != nil {

View File

@@ -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
}

View File

@@ -2,21 +2,51 @@ package main
import ( import (
"ai-gateway/api" "ai-gateway/api"
"ai-gateway/internal/config"
"ai-gateway/internal/db" "ai-gateway/internal/db"
"ai-gateway/internal/middleware" "ai-gateway/internal/middleware"
"ai-gateway/internal/scheduler"
"log" "log"
"os"
"os/signal"
"syscall"
"github.com/gin-contrib/cors" "github.com/gin-contrib/cors"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
func main() { func main() {
// 初始化数据库连接 log.Println("🚀 启动 AI Gateway 服务...")
database, err := db.InitDB()
if err != nil { // 加载配置文件(使用固定路径,便于 Docker 挂载)
log.Fatalf("Failed to initialize database: %v", err) 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路由器实例 // 创建Gin路由器实例
router := gin.Default() router := gin.Default()
@@ -36,7 +66,8 @@ func main() {
// 创建API处理器 // 创建API处理器
handler := &api.APIHandler{ handler := &api.APIHandler{
DB: database, DB: database,
LogCleaner: logCleaner,
} }
// 添加健康检查端点(无需认证) // 添加健康检查端点(无需认证)
@@ -83,11 +114,28 @@ func main() {
api_.GET("/logs/stats", handler.GetRequestLogStatsHandler) api_.GET("/logs/stats", handler.GetRequestLogStatsHandler)
api_.GET("/logs/:id", handler.GetRequestLogDetailHandler) api_.GET("/logs/:id", handler.GetRequestLogDetailHandler)
api_.DELETE("/logs", handler.ClearRequestLogsHandler) 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") quit := make(chan os.Signal, 1)
if err := router.Run(":8080"); err != nil { signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
log.Fatalf("Failed to start server: %v", err)
} // 在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 服务已关闭")
} }

18
config.example.json Normal file
View File

@@ -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"
}
}