Files
QWQLwToo 079ee4eaeb
build-winui / winui (push) Has been cancelled
Add server components
2026-06-26 13:28:09 +08:00

448 lines
11 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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,
})
}