package handlers import ( "fmt" "net/http" "os" "path/filepath" "strings" "time" "software-download-center/database" "software-download-center/models" "software-download-center/utils" "github.com/gin-gonic/gin" "gorm.io/gorm" ) // LogEntry 日志条目 type LogEntry struct { Time string `json:"time"` Level string `json:"level"` Message string `json:"message"` } var logBuffer []LogEntry var maxLogEntries = 1000 // AddLog 添加日志到缓冲区 func AddLog(level, message string) { entry := LogEntry{ Time: time.Now().Format("2006-01-02 15:04:05"), Level: level, Message: message, } logBuffer = append(logBuffer, entry) // 同时输出到控制台(使用 utils.Logger) logger := utils.NewLogger() switch level { case "ERROR": logger.Error(message) case "WARN": logger.Warn(message) case "INFO": logger.Info(message) default: logger.System(message) } // 保持缓冲区大小 if len(logBuffer) > maxLogEntries { logBuffer = logBuffer[len(logBuffer)-maxLogEntries:] } } // GetLogBuffer 获取日志缓冲区(用于外部访问) func GetLogBuffer() []LogEntry { return logBuffer } // GetLogs 获取日志 func GetLogs(c *gin.Context) { limit := 100 if limitStr := c.Query("limit"); limitStr != "" { fmt.Sscanf(limitStr, "%d", &limit) } start := len(logBuffer) - limit if start < 0 { start = 0 } logs := logBuffer[start:] c.JSON(http.StatusOK, gin.H{ "logs": logs, "total": len(logBuffer), }) } // GetRoutes 获取所有路由 func GetRoutes(c *gin.Context) { var routes []models.Route if err := database.DB.Order("`order` ASC, id ASC").Find(&routes).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": "获取路由失败", }) return } c.JSON(http.StatusOK, gin.H{ "routes": routes, }) } // CreateRoute 创建路由 func CreateRoute(c *gin.Context) { var route models.Route if err := c.ShouldBindJSON(&route); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": "请求参数错误: " + err.Error(), }) return } // 验证路径是否已存在 var existingRoute models.Route if err := database.DB.Where("path = ? AND method = ?", route.Path, route.Method).First(&existingRoute).Error; err == nil { AddLog("WARN", fmt.Sprintf("创建路由失败(已存在): %s %s (IP: %s)", route.Method, route.Path, c.ClientIP())) c.JSON(http.StatusConflict, gin.H{ "error": "路由已存在", }) return } if err := database.DB.Create(&route).Error; err != nil { AddLog("ERROR", fmt.Sprintf("创建路由数据库错误: %s %s - %s (IP: %s)", route.Method, route.Path, err.Error(), c.ClientIP())) c.JSON(http.StatusInternalServerError, gin.H{ "error": "创建路由失败", }) return } AddLog("INFO", fmt.Sprintf("创建路由成功: %s %s (类型: %s, IP: %s)", route.Method, route.Path, route.Type, c.ClientIP())) c.JSON(http.StatusOK, gin.H{ "message": "路由创建成功", "route": route, }) } // UpdateRoute 更新路由 func UpdateRoute(c *gin.Context) { id := c.Param("id") var route models.Route oldPath := route.Path oldMethod := route.Method if err := database.DB.First(&route, id).Error; err != nil { if err == gorm.ErrRecordNotFound { AddLog("WARN", fmt.Sprintf("更新路由失败(不存在): ID=%s (IP: %s)", id, c.ClientIP())) c.JSON(http.StatusNotFound, gin.H{ "error": "路由不存在", }) return } AddLog("ERROR", fmt.Sprintf("查询路由失败: ID=%s - %s (IP: %s)", id, err.Error(), c.ClientIP())) c.JSON(http.StatusInternalServerError, gin.H{ "error": "查询路由失败", }) return } oldPath = route.Path oldMethod = route.Method if err := c.ShouldBindJSON(&route); err != nil { AddLog("WARN", fmt.Sprintf("更新路由请求参数错误: ID=%s - %s (IP: %s)", id, err.Error(), c.ClientIP())) c.JSON(http.StatusBadRequest, gin.H{ "error": "请求参数错误", }) return } if err := database.DB.Save(&route).Error; err != nil { AddLog("ERROR", fmt.Sprintf("更新路由数据库错误: %s %s - %s (IP: %s)", route.Method, route.Path, err.Error(), c.ClientIP())) c.JSON(http.StatusInternalServerError, gin.H{ "error": "更新路由失败", }) return } AddLog("INFO", fmt.Sprintf("更新路由成功: %s %s -> %s %s (IP: %s)", oldMethod, oldPath, route.Method, route.Path, c.ClientIP())) c.JSON(http.StatusOK, gin.H{ "message": "路由更新成功", "route": route, }) } // DeleteRoute 删除路由 func DeleteRoute(c *gin.Context) { id := c.Param("id") var route models.Route if err := database.DB.First(&route, id).Error; err != nil { if err == gorm.ErrRecordNotFound { AddLog("WARN", fmt.Sprintf("删除路由失败(不存在): ID=%s (IP: %s)", id, c.ClientIP())) c.JSON(http.StatusNotFound, gin.H{ "error": "路由不存在", }) return } AddLog("ERROR", fmt.Sprintf("查询路由失败: ID=%s - %s (IP: %s)", id, err.Error(), c.ClientIP())) c.JSON(http.StatusInternalServerError, gin.H{ "error": "查询路由失败", }) return } routeInfo := fmt.Sprintf("%s %s", route.Method, route.Path) if err := database.DB.Delete(&route).Error; err != nil { AddLog("ERROR", fmt.Sprintf("删除路由数据库错误: %s - %s (IP: %s)", routeInfo, err.Error(), c.ClientIP())) c.JSON(http.StatusInternalServerError, gin.H{ "error": "删除路由失败", }) return } AddLog("INFO", fmt.Sprintf("删除路由成功: %s (IP: %s)", routeInfo, c.ClientIP())) c.JSON(http.StatusOK, gin.H{ "message": "路由删除成功", }) } // GetFiles 获取文件列表 func GetFiles(c *gin.Context) { dir := c.Query("dir") if dir == "" { dir = "public/downloads" } // 安全检查:防止目录遍历 if strings.Contains(dir, "..") { c.JSON(http.StatusBadRequest, gin.H{ "error": "无效的目录路径", }) return } files, err := os.ReadDir(dir) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": "读取目录失败", }) return } var fileList []gin.H for _, file := range files { info, _ := file.Info() fileList = append(fileList, gin.H{ "name": file.Name(), "size": info.Size(), "mod_time": info.ModTime().Format("2006-01-02 15:04:05"), "is_dir": file.IsDir(), }) } c.JSON(http.StatusOK, gin.H{ "files": fileList, "path": dir, }) } // SaveFile 保存文件 func SaveFile(c *gin.Context) { var req struct { Path string `json:"path" binding:"required"` Content string `json:"content" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": "请求参数错误", }) return } // 安全检查 if strings.Contains(req.Path, "..") { c.JSON(http.StatusBadRequest, gin.H{ "error": "无效的文件路径", }) return } // 确保目录存在 dir := filepath.Dir(req.Path) if err := os.MkdirAll(dir, 0755); err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": "创建目录失败", }) return } // 保存文件 if err := os.WriteFile(req.Path, []byte(req.Content), 0644); err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": "保存文件失败", }) return } AddLog("INFO", fmt.Sprintf("保存文件: %s", req.Path)) c.JSON(http.StatusOK, gin.H{ "message": "文件保存成功", }) } // ReadFile 读取文件 func ReadFile(c *gin.Context) { path := c.Query("path") if path == "" { c.JSON(http.StatusBadRequest, gin.H{ "error": "缺少文件路径参数", }) return } // 安全检查 if strings.Contains(path, "..") { c.JSON(http.StatusBadRequest, gin.H{ "error": "无效的文件路径", }) return } content, err := os.ReadFile(path) if err != nil { AddLog("ERROR", fmt.Sprintf("读取文件失败: %s - %s (IP: %s)", path, err.Error(), c.ClientIP())) c.JSON(http.StatusInternalServerError, gin.H{ "error": "读取文件失败", }) return } AddLog("INFO", fmt.Sprintf("读取文件: %s (大小: %d 字节, IP: %s)", path, len(content), c.ClientIP())) c.JSON(http.StatusOK, gin.H{ "content": string(content), "path": path, "size": len(content), }) } // ReloadRoutes 热重载路由和配置 func ReloadRoutes(c *gin.Context) { var req struct { Type string `json:"type"` // "config" 或 "all" } if err := c.ShouldBindJSON(&req); err != nil { req.Type = "all" } if req.Type == "config" || req.Type == "all" { // 重新加载所有配置文件 files := []string{"tool-status.json", "update-info.json", "media-types.json"} successCount := 0 for _, file := range files { if _, err := os.Stat(filepath.Join("public", file)); err == nil { if err := utils.ReloadConfig(file); err == nil { successCount++ } } } AddLog("INFO", fmt.Sprintf("重新加载配置文件: %d/%d 成功 (IP: %s)", successCount, len(files), c.ClientIP())) } // 注意:Gin 不支持动态路由重载,路由更改需要重启服务器 if req.Type == "routes" || req.Type == "all" { AddLog("INFO", fmt.Sprintf("路由热重载请求(需要重启服务器才能生效)(IP: %s)", c.ClientIP())) c.JSON(http.StatusOK, gin.H{ "message": "配置已重新加载", "note": "路由更改需要重启服务器才能生效,建议使用进程管理器(如 systemd、supervisor)", }) return } c.JSON(http.StatusOK, gin.H{ "message": "配置已重新加载", }) } // GetSystemInfo 获取系统信息 func GetSystemInfo(c *gin.Context) { var userCount int64 var routeCount int64 database.DB.Model(&models.User{}).Count(&userCount) database.DB.Model(&models.Route{}).Count(&routeCount) c.JSON(http.StatusOK, gin.H{ "users": userCount, "routes": routeCount, "logs": len(logBuffer), "version": "1.0.0", "server_time": time.Now().Format("2006-01-02 15:04:05"), }) } // UpdateJSONConfig 更新 JSON 配置文件 func UpdateJSONConfig(c *gin.Context) { var req struct { File string `json:"file" binding:"required"` Content map[string]interface{} `json:"content" binding:"required"` Reload bool `json:"reload"` // 是否立即重新加载 } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": "请求参数错误", }) return } // 验证文件路径 allowedFiles := []string{"tool-status.json", "update-info.json", "media-types.json"} allowed := false for _, f := range allowedFiles { if req.File == f { allowed = true break } } if !allowed { c.JSON(http.StatusBadRequest, gin.H{ "error": "不允许修改此文件", }) return } // 使用配置工具保存并更新缓存 if err := utils.SaveConfig(req.File, req.Content); err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": "保存文件失败: " + err.Error(), }) return } message := "配置文件更新成功" if req.Reload { // 重新加载配置到缓存 if err := utils.ReloadConfig(req.File); err != nil { AddLog("WARN", fmt.Sprintf("配置文件已保存但重新加载失败: %s - %s", req.File, err.Error())) message = "配置文件已保存,但重新加载时出现警告" } else { AddLog("INFO", fmt.Sprintf("配置文件已保存并立即加载: %s", req.File)) message = "配置文件已保存并立即生效" } } else { AddLog("INFO", fmt.Sprintf("更新配置文件: %s", req.File)) } c.JSON(http.StatusOK, gin.H{ "message": message, "file": req.File, }) }