@@ -0,0 +1,447 @@
|
||||
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,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user