2026-03-18 21:51:17 +08:00
|
|
|
|
package install
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"NetworkAuth/config"
|
|
|
|
|
|
"NetworkAuth/database"
|
|
|
|
|
|
"NetworkAuth/models"
|
|
|
|
|
|
"NetworkAuth/services"
|
|
|
|
|
|
"NetworkAuth/utils"
|
2026-03-28 23:30:02 +08:00
|
|
|
|
"fmt"
|
2026-03-18 21:51:17 +08:00
|
|
|
|
"net/http"
|
|
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
|
|
|
|
"github.com/gin-gonic/gin"
|
2026-03-31 02:14:08 +08:00
|
|
|
|
"github.com/sirupsen/logrus"
|
2026-03-28 23:30:02 +08:00
|
|
|
|
"gorm.io/driver/mysql"
|
|
|
|
|
|
"gorm.io/gorm"
|
2026-03-18 21:51:17 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// InstallSubmitHandler 处理安装表单提交
|
|
|
|
|
|
func InstallSubmitHandler(c *gin.Context) {
|
|
|
|
|
|
var req struct {
|
|
|
|
|
|
// 数据库配置
|
|
|
|
|
|
DbType string `json:"db_type" binding:"required,oneof=sqlite mysql"`
|
|
|
|
|
|
DbHost string `json:"db_host"`
|
|
|
|
|
|
DbPort int `json:"db_port"`
|
|
|
|
|
|
DbName string `json:"db_name"`
|
|
|
|
|
|
DbUser string `json:"db_user"`
|
|
|
|
|
|
DbPass string `json:"db_pass"`
|
|
|
|
|
|
|
|
|
|
|
|
// 站点和管理员配置
|
|
|
|
|
|
SiteTitle string `json:"site_title" binding:"required"`
|
|
|
|
|
|
AdminUsername string `json:"admin_username" binding:"required"`
|
|
|
|
|
|
AdminPassword string `json:"admin_password" binding:"required,min=6"`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
|
|
|
|
c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "参数错误: " + err.Error()})
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 更新配置文件
|
|
|
|
|
|
err := config.UpdateConfig(func(cfg *config.AppConfig) {
|
|
|
|
|
|
cfg.Database.Type = req.DbType
|
|
|
|
|
|
if req.DbType == "mysql" {
|
|
|
|
|
|
cfg.Database.MySQL.Host = req.DbHost
|
|
|
|
|
|
cfg.Database.MySQL.Port = req.DbPort
|
|
|
|
|
|
cfg.Database.MySQL.Database = req.DbName
|
|
|
|
|
|
cfg.Database.MySQL.Username = req.DbUser
|
|
|
|
|
|
cfg.Database.MySQL.Password = req.DbPass
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": "更新配置文件失败: " + err.Error()})
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-28 23:30:02 +08:00
|
|
|
|
// 2. 使用新配置尝试连接数据库
|
|
|
|
|
|
var testDB *gorm.DB
|
|
|
|
|
|
if req.DbType == "mysql" {
|
|
|
|
|
|
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
|
|
|
|
|
|
req.DbUser, req.DbPass, req.DbHost, req.DbPort, req.DbName)
|
|
|
|
|
|
testDB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": "连接 MySQL 数据库失败,请检查配置是否正确: " + err.Error()})
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
sqlDB, err := testDB.DB()
|
|
|
|
|
|
if err != nil || sqlDB.Ping() != nil {
|
|
|
|
|
|
c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": "连接 MySQL 数据库失败,无法 Ping 通,请检查配置是否正确"})
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 重新初始化全局数据库连接并执行迁移
|
2026-03-18 21:51:17 +08:00
|
|
|
|
db, err := database.ReInit()
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": "连接数据库失败: " + err.Error()})
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if db == nil {
|
2026-03-28 23:30:02 +08:00
|
|
|
|
c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": "获取数据库实例失败,请检查数据库配置是否正确"})
|
2026-03-18 21:51:17 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 强制执行迁移确保表存在
|
|
|
|
|
|
if err := database.AutoMigrate(); err != nil {
|
|
|
|
|
|
c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": "初始化数据表失败: " + err.Error()})
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化系统默认设置
|
|
|
|
|
|
database.SeedDefaultSettings()
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 生成新的管理员密码哈希和盐值
|
|
|
|
|
|
adminSalt, err := utils.GenerateRandomSalt()
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": "生成盐值失败"})
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
adminPasswordHash, err := utils.HashPasswordWithSalt(req.AdminPassword, adminSalt)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": "加密密码失败"})
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-28 23:30:02 +08:00
|
|
|
|
// 开启事务进行更新
|
|
|
|
|
|
tx := db.Begin()
|
|
|
|
|
|
|
|
|
|
|
|
// 更新或创建超级管理员账号
|
|
|
|
|
|
var adminUser models.User
|
|
|
|
|
|
if err := tx.Where("uuid = ?", "00000000-0000-0000-0000-000000000000").First(&adminUser).Error; err != nil {
|
|
|
|
|
|
// 如果不存在则创建
|
|
|
|
|
|
adminUser = models.User{
|
|
|
|
|
|
UUID: "00000000-0000-0000-0000-000000000000",
|
|
|
|
|
|
Username: strings.TrimSpace(req.AdminUsername),
|
|
|
|
|
|
Password: adminPasswordHash,
|
|
|
|
|
|
PasswordSalt: adminSalt,
|
|
|
|
|
|
Nickname: "管理员",
|
|
|
|
|
|
Avatar: "",
|
|
|
|
|
|
Role: 0,
|
|
|
|
|
|
Status: 1,
|
|
|
|
|
|
Remark: "系统默认超级管理员",
|
|
|
|
|
|
}
|
|
|
|
|
|
// 使用 Select("Role") 确保 Role 字段(值为0时是零值)被显式插入,避免使用数据库默认值 1
|
|
|
|
|
|
if err := tx.Select("UUID", "Username", "Password", "PasswordSalt", "Nickname", "Avatar", "Role", "Status", "Remark").Create(&adminUser).Error; err != nil {
|
|
|
|
|
|
tx.Rollback()
|
|
|
|
|
|
c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": "创建管理员账号失败"})
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 存在则更新
|
|
|
|
|
|
adminUser.Username = strings.TrimSpace(req.AdminUsername)
|
|
|
|
|
|
adminUser.Password = adminPasswordHash
|
|
|
|
|
|
adminUser.PasswordSalt = adminSalt
|
|
|
|
|
|
adminUser.Nickname = "管理员"
|
|
|
|
|
|
adminUser.Role = 0
|
|
|
|
|
|
if err := tx.Save(&adminUser).Error; err != nil {
|
|
|
|
|
|
tx.Rollback()
|
|
|
|
|
|
c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": "更新管理员账号失败"})
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
// 确保角色被更新为0(GORM的Save可能忽略零值,所以额外Update一次)
|
|
|
|
|
|
tx.Model(&adminUser).Update("Role", 0)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果是新创建的,再额外确保一次 Role 为 0,避免 default 标签导致的零值问题
|
|
|
|
|
|
tx.Model(&adminUser).Update("Role", 0)
|
|
|
|
|
|
|
2026-03-18 21:51:17 +08:00
|
|
|
|
// 4. 更新设置表
|
|
|
|
|
|
settingsToUpdate := map[string]string{
|
2026-03-28 23:30:02 +08:00
|
|
|
|
"site_title": req.SiteTitle,
|
|
|
|
|
|
"is_installed": "1", // 标记为已安装
|
2026-03-18 21:51:17 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for name, value := range settingsToUpdate {
|
|
|
|
|
|
// 先尝试更新,如果没有该记录,则忽略(因为 AutoMigrate 已经创建了默认记录)
|
|
|
|
|
|
if err := tx.Model(&models.Settings{}).Where("name = ?", name).Update("value", value).Error; err != nil {
|
|
|
|
|
|
tx.Rollback()
|
|
|
|
|
|
c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": "保存设置失败: " + name})
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
tx.Commit()
|
|
|
|
|
|
|
|
|
|
|
|
// 5. 更新内存缓存
|
2026-03-28 23:30:02 +08:00
|
|
|
|
services.ResetSettingsService()
|
2026-03-18 21:51:17 +08:00
|
|
|
|
|
2026-03-31 02:14:08 +08:00
|
|
|
|
// 6. 动态初始化核心组件
|
|
|
|
|
|
// 在系统安装完成后,执行本来在 server.go 中需要已安装才能执行的初始化逻辑
|
|
|
|
|
|
encryptionKey := services.GetSettingsService().GetEncryptionKey()
|
|
|
|
|
|
if err := utils.InitEncryption(encryptionKey); err != nil {
|
|
|
|
|
|
logrus.WithError(err).Error("安装完成后加密管理器初始化失败")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 启动日志清理定时任务
|
|
|
|
|
|
services.StartLogCleanupTask()
|
|
|
|
|
|
|
2026-03-18 21:51:17 +08:00
|
|
|
|
c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "安装成功"})
|
|
|
|
|
|
}
|