Add server components
build-winui / winui (push) Has been cancelled

This commit is contained in:
QWQLwToo
2026-06-26 13:28:09 +08:00
parent 7ecc6a8923
commit 079ee4eaeb
168 changed files with 37475 additions and 0 deletions
+447
View File
@@ -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,
})
}
+263
View 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,
},
})
}
+219
View File
@@ -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,
})
}
+107
View File
@@ -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
}