@@ -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,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"software-download-center/database"
|
||||
"software-download-center/models"
|
||||
"software-download-center/utils"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// RegisterRequest 注册请求
|
||||
type RegisterRequest struct {
|
||||
Username string `json:"username" binding:"required,min=3,max=50"`
|
||||
Email string `json:"email" binding:"required,email"`
|
||||
Password string `json:"password" binding:"required,min=8"`
|
||||
}
|
||||
|
||||
// LoginRequest 登录请求
|
||||
type LoginRequest struct {
|
||||
Username string `json:"username" binding:"required"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
}
|
||||
|
||||
// Register 用户注册
|
||||
func Register(c *gin.Context) {
|
||||
var req RegisterRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "请求参数错误: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 验证密码强度
|
||||
if err := utils.ValidatePasswordStrength(req.Password); err != nil {
|
||||
AddLog("WARN", fmt.Sprintf("注册失败(密码强度不足): 用户名=%s, IP=%s", req.Username, c.ClientIP()))
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 检查用户名是否已存在
|
||||
var existingUser models.User
|
||||
if err := database.DB.Where("username = ?", req.Username).First(&existingUser).Error; err == nil {
|
||||
AddLog("WARN", fmt.Sprintf("注册失败(用户名已存在): 用户名=%s, IP=%s", req.Username, c.ClientIP()))
|
||||
c.JSON(http.StatusConflict, gin.H{
|
||||
"error": "用户名已存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 检查邮箱是否已存在
|
||||
if err := database.DB.Where("email = ?", req.Email).First(&existingUser).Error; err == nil {
|
||||
AddLog("WARN", fmt.Sprintf("注册失败(邮箱已被注册): 邮箱=%s, IP=%s", req.Email, c.ClientIP()))
|
||||
c.JSON(http.StatusConflict, gin.H{
|
||||
"error": "邮箱已被注册",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 检查是否是第一个用户(自动成为管理员)
|
||||
var userCount int64
|
||||
database.DB.Model(&models.User{}).Count(&userCount)
|
||||
isAdmin := userCount == 0
|
||||
|
||||
// 加密密码
|
||||
hashedPassword, err := utils.HashPassword(req.Password)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "密码加密失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 创建用户
|
||||
user := models.User{
|
||||
Username: req.Username,
|
||||
Email: req.Email,
|
||||
Password: hashedPassword,
|
||||
IsAdmin: isAdmin,
|
||||
IsActive: true,
|
||||
}
|
||||
|
||||
if err := database.DB.Create(&user).Error; err != nil {
|
||||
AddLog("ERROR", fmt.Sprintf("创建用户数据库错误: 用户名=%s - %s, IP=%s", req.Username, err.Error(), c.ClientIP()))
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "创建用户失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
AddLog("INFO", fmt.Sprintf("用户注册成功: 用户名=%s, 邮箱=%s, 管理员=%v, IP=%s", user.Username, user.Email, user.IsAdmin, c.ClientIP()))
|
||||
|
||||
// 生成 token
|
||||
token, err := utils.GenerateToken(user.ID, user.Username, user.IsAdmin)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "生成 token 失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 设置 cookie
|
||||
c.SetCookie("token", token, 24*3600, "/", "", false, true)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "注册成功",
|
||||
"token": token,
|
||||
"user": gin.H{
|
||||
"id": user.ID,
|
||||
"username": user.Username,
|
||||
"email": user.Email,
|
||||
"is_admin": user.IsAdmin,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Login 用户登录
|
||||
func Login(c *gin.Context) {
|
||||
var req LoginRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "请求参数错误",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 检查数据库是否已初始化
|
||||
if !database.IsDBInitialized() {
|
||||
// 使用默认管理员账号
|
||||
if req.Username == "admin" && req.Password == "admin123456" {
|
||||
// 生成临时 token(用于安装页面)
|
||||
token, err := utils.GenerateToken(0, "admin", true)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "生成 token 失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
AddLog("INFO", fmt.Sprintf("默认管理员登录成功: 用户名=%s, IP=%s", req.Username, c.ClientIP()))
|
||||
c.SetCookie("token", token, 24*3600, "/", "", false, true)
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "登录成功,请配置数据库",
|
||||
"token": token,
|
||||
"user": gin.H{
|
||||
"id": 0,
|
||||
"username": "admin",
|
||||
"is_admin": true,
|
||||
"is_setup": false, // 标记需要安装
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"error": "数据库未初始化,请使用默认管理员账号登录(admin/admin123456)",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 查找用户
|
||||
var user models.User
|
||||
if err := database.DB.Where("username = ?", req.Username).First(&user).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
AddLog("WARN", fmt.Sprintf("登录失败(用户不存在): 用户名=%s, IP=%s", req.Username, c.ClientIP()))
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"error": "用户名或密码错误",
|
||||
})
|
||||
return
|
||||
}
|
||||
AddLog("ERROR", fmt.Sprintf("查询用户失败: 用户名=%s - %s, IP=%s", req.Username, err.Error(), c.ClientIP()))
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "查询用户失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 检查用户是否激活
|
||||
if !user.IsActive {
|
||||
AddLog("WARN", fmt.Sprintf("登录失败(账户已禁用): 用户名=%s, IP=%s", req.Username, c.ClientIP()))
|
||||
c.JSON(http.StatusForbidden, gin.H{
|
||||
"error": "账户已被禁用",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 验证密码
|
||||
if !utils.CheckPassword(req.Password, user.Password) {
|
||||
AddLog("WARN", fmt.Sprintf("登录失败(密码错误): 用户名=%s, IP=%s", req.Username, c.ClientIP()))
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"error": "用户名或密码错误",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
AddLog("INFO", fmt.Sprintf("用户登录成功: 用户名=%s, 管理员=%v, IP=%s", user.Username, user.IsAdmin, c.ClientIP()))
|
||||
|
||||
// 生成 token
|
||||
token, err := utils.GenerateToken(user.ID, user.Username, user.IsAdmin)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "生成 token 失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 设置 cookie
|
||||
c.SetCookie("token", token, 24*3600, "/", "", false, true)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "登录成功",
|
||||
"token": token,
|
||||
"user": gin.H{
|
||||
"id": user.ID,
|
||||
"username": user.Username,
|
||||
"email": user.Email,
|
||||
"is_admin": user.IsAdmin,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Logout 用户登出
|
||||
func Logout(c *gin.Context) {
|
||||
c.SetCookie("token", "", -1, "/", "", false, true)
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "登出成功",
|
||||
})
|
||||
}
|
||||
|
||||
// GetCurrentUser 获取当前用户信息
|
||||
func GetCurrentUser(c *gin.Context) {
|
||||
userID, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"error": "未授权",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var user models.User
|
||||
if err := database.DB.First(&user, userID).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{
|
||||
"error": "用户不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"user": gin.H{
|
||||
"id": user.ID,
|
||||
"username": user.Username,
|
||||
"email": user.Email,
|
||||
"is_admin": user.IsAdmin,
|
||||
"is_active": user.IsActive,
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"software-download-center/database"
|
||||
"software-download-center/utils"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// GetDatabaseInfo 获取数据库信息
|
||||
func GetDatabaseInfo(c *gin.Context) {
|
||||
dbType := database.GetDBType()
|
||||
osInfo := utils.GetOSInfo()
|
||||
|
||||
var dbInfo gin.H
|
||||
if dbType == "mysql" {
|
||||
dbInfo = gin.H{
|
||||
"type": "MySQL",
|
||||
"status": "connected",
|
||||
}
|
||||
} else {
|
||||
dbInfo = gin.H{
|
||||
"type": "SQLite",
|
||||
"status": "connected",
|
||||
"file": "data/app.db",
|
||||
"cgo_support": osInfo.IsCGO,
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"database": dbInfo,
|
||||
"os": gin.H{
|
||||
"os": osInfo.OS,
|
||||
"arch": osInfo.Arch,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// ConvertDatabaseRequest 数据库转换请求
|
||||
type ConvertDatabaseRequest struct {
|
||||
TargetType string `json:"target_type" binding:"required,oneof=sqlite mysql"`
|
||||
}
|
||||
|
||||
// ConvertDatabase 转换数据库
|
||||
func ConvertDatabase(c *gin.Context) {
|
||||
var req ConvertDatabaseRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "请求参数错误: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
logger := utils.NewLogger()
|
||||
|
||||
if err := database.ConvertDatabase(req.TargetType, logger); err != nil {
|
||||
AddLog("ERROR", fmt.Sprintf("数据库转换失败: %s", err.Error()))
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "数据库转换失败: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
AddLog("INFO", fmt.Sprintf("数据库转换成功: %s", req.TargetType))
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": fmt.Sprintf("数据库已成功转换为 %s", strings.ToUpper(req.TargetType)),
|
||||
"type": req.TargetType,
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateDatabasePasswordRequest 更新数据库密码请求
|
||||
type UpdateDatabasePasswordRequest struct {
|
||||
CurrentPassword string `json:"current_password" binding:"required"`
|
||||
NewPassword string `json:"new_password" binding:"required,min=1"`
|
||||
ConfirmPassword string `json:"confirm_password" binding:"required"`
|
||||
}
|
||||
|
||||
// UpdateDatabasePassword 更新数据库 root 密码
|
||||
func UpdateDatabasePassword(c *gin.Context) {
|
||||
var req UpdateDatabasePasswordRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
AddLog("WARN", fmt.Sprintf("更新数据库密码请求参数错误: %s (IP: %s)", err.Error(), c.ClientIP()))
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "请求参数错误: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 验证新密码和确认密码是否一致
|
||||
if req.NewPassword != req.ConfirmPassword {
|
||||
AddLog("WARN", fmt.Sprintf("更新数据库密码失败(密码不一致)(IP: %s)", c.ClientIP()))
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "新密码和确认密码不一致",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 检查当前数据库类型
|
||||
dbType := database.GetDBType()
|
||||
if dbType != "mysql" {
|
||||
AddLog("WARN", fmt.Sprintf("更新数据库密码失败(当前数据库不是 MySQL)(IP: %s)", c.ClientIP()))
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "当前数据库类型不是 MySQL,无法修改密码",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 验证当前密码
|
||||
if err := database.VerifyMySQLPassword(req.CurrentPassword); err != nil {
|
||||
AddLog("WARN", fmt.Sprintf("更新数据库密码失败(当前密码验证失败)(IP: %s)", c.ClientIP()))
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"error": "当前密码错误",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 更新密码
|
||||
if err := database.UpdateMySQLPassword(req.NewPassword); err != nil {
|
||||
AddLog("ERROR", fmt.Sprintf("更新数据库密码失败: %s (IP: %s)", err.Error(), c.ClientIP()))
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "更新密码失败: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
AddLog("INFO", fmt.Sprintf("数据库 root 密码更新成功 (IP: %s)", c.ClientIP()))
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "数据库密码更新成功!请更新环境变量 DB_PASSWORD 并重启服务器以使新密码生效。",
|
||||
})
|
||||
}
|
||||
|
||||
// GetDatabaseConfig 获取数据库配置信息(不包含敏感信息)
|
||||
func GetDatabaseConfig(c *gin.Context) {
|
||||
config := database.GetDatabaseConfig()
|
||||
|
||||
// 隐藏敏感信息
|
||||
safeConfig := gin.H{
|
||||
"type": config.Type,
|
||||
"host": config.Host,
|
||||
"port": config.Port,
|
||||
"user": config.User,
|
||||
"database": config.Database,
|
||||
"table_prefix": config.TablePrefix,
|
||||
"has_password": config.Password != "",
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"config": safeConfig,
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateDatabaseConfigRequest 更新数据库配置请求
|
||||
type UpdateDatabaseConfigRequest struct {
|
||||
Type string `json:"type" binding:"required,oneof=sqlite mysql"`
|
||||
Host string `json:"host"`
|
||||
Port string `json:"port"`
|
||||
User string `json:"user"`
|
||||
Password string `json:"password"`
|
||||
Database string `json:"database"`
|
||||
TablePrefix string `json:"table_prefix"`
|
||||
DSN string `json:"dsn"`
|
||||
}
|
||||
|
||||
// UpdateDatabaseConfig 更新数据库配置(需要重新连接)
|
||||
func UpdateDatabaseConfig(c *gin.Context) {
|
||||
var req UpdateDatabaseConfigRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "请求参数错误: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 验证必填字段
|
||||
if req.Type == "mysql" {
|
||||
if req.Host == "" || req.Port == "" || req.User == "" || req.Database == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "MySQL 配置不完整:需要 host, port, user, database",
|
||||
})
|
||||
return
|
||||
}
|
||||
} else if req.Type == "sqlite" {
|
||||
if req.DSN == "" {
|
||||
req.DSN = "data"
|
||||
}
|
||||
}
|
||||
|
||||
// 构建数据库配置
|
||||
config := &database.DatabaseConfig{
|
||||
Type: req.Type,
|
||||
Host: req.Host,
|
||||
Port: req.Port,
|
||||
User: req.User,
|
||||
Password: req.Password,
|
||||
Database: req.Database,
|
||||
TablePrefix: req.TablePrefix,
|
||||
DSN: req.DSN,
|
||||
}
|
||||
|
||||
// 测试连接
|
||||
if err := database.InitDBWithConfig(config); err != nil {
|
||||
AddLog("ERROR", fmt.Sprintf("数据库配置更新失败: %s (IP: %s)", err.Error(), c.ClientIP()))
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "数据库连接失败: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
AddLog("INFO", fmt.Sprintf("数据库配置更新成功: 类型=%s (IP: %s)", req.Type, c.ClientIP()))
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "数据库配置已更新,请重启服务器以应用更改",
|
||||
"type": req.Type,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"software-download-center/database"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// DefaultAdminUsername 默认管理员用户名
|
||||
const DefaultAdminUsername = "admin"
|
||||
|
||||
// DefaultAdminPassword 默认管理员密码
|
||||
const DefaultAdminPassword = "admin123456"
|
||||
|
||||
// CheckInstallStatus 检查安装状态
|
||||
func CheckInstallStatus(c *gin.Context) {
|
||||
isInitialized := database.IsDBInitialized()
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"initialized": isInitialized,
|
||||
"default_admin": gin.H{
|
||||
"username": DefaultAdminUsername,
|
||||
"password": DefaultAdminPassword,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// InstallDatabaseRequest 数据库安装请求
|
||||
type InstallDatabaseRequest struct {
|
||||
Type string `json:"type" binding:"required,oneof=sqlite mysql"`
|
||||
Host string `json:"host"`
|
||||
Port string `json:"port"`
|
||||
User string `json:"user"`
|
||||
Password string `json:"password"`
|
||||
Database string `json:"database"`
|
||||
TablePrefix string `json:"table_prefix"`
|
||||
DSN string `json:"dsn"` // SQLite 数据目录
|
||||
}
|
||||
|
||||
// InstallDatabase 安装数据库
|
||||
func InstallDatabase(c *gin.Context) {
|
||||
// 检查是否已初始化
|
||||
if database.IsDBInitialized() {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "数据库已初始化,请使用设置页面修改配置",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var req InstallDatabaseRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "请求参数错误: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 验证必填字段
|
||||
if req.Type == "mysql" {
|
||||
if req.Host == "" || req.Port == "" || req.User == "" || req.Database == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "MySQL 配置不完整:需要 host, port, user, database",
|
||||
})
|
||||
return
|
||||
}
|
||||
} else if req.Type == "sqlite" {
|
||||
if req.DSN == "" {
|
||||
req.DSN = "data"
|
||||
}
|
||||
}
|
||||
|
||||
// 构建数据库配置
|
||||
config := &database.DatabaseConfig{
|
||||
Type: req.Type,
|
||||
Host: req.Host,
|
||||
Port: req.Port,
|
||||
User: req.User,
|
||||
Password: req.Password,
|
||||
Database: req.Database,
|
||||
TablePrefix: req.TablePrefix,
|
||||
DSN: req.DSN,
|
||||
}
|
||||
|
||||
// 初始化数据库
|
||||
if err := database.InitDBWithConfig(config); err != nil {
|
||||
AddLog("ERROR", fmt.Sprintf("数据库安装失败: %s (IP: %s)", err.Error(), c.ClientIP()))
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "数据库连接失败: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
AddLog("INFO", fmt.Sprintf("数据库安装成功: 类型=%s (IP: %s)", req.Type, c.ClientIP()))
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "数据库安装成功",
|
||||
"type": req.Type,
|
||||
})
|
||||
}
|
||||
|
||||
// VerifyDefaultAdmin 验证默认管理员登录
|
||||
func VerifyDefaultAdmin(username, password string) bool {
|
||||
return username == DefaultAdminUsername && password == DefaultAdminPassword
|
||||
}
|
||||
Reference in New Issue
Block a user