Files

233 lines
7.6 KiB
Go
Raw Permalink Normal View History

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"
"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) {
2026-04-04 20:50:45 +08:00
// 二次安全校验:检查系统是否已经安装
isInstalledStr := services.GetSettingsService().GetString("is_installed", "0")
if isInstalledStr == "1" {
c.JSON(http.StatusForbidden, gin.H{"code": 403, "msg": "系统已安装,禁止重复初始化"})
return
}
2026-03-18 21:51:17 +08:00
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"`
// Redis配置可选
RedisEnabled bool `json:"redis_enabled"`
RedisHost string `json:"redis_host"`
RedisPort int `json:"redis_port"`
RedisPassword string `json:"redis_password"`
RedisDB int `json:"redis_db"`
2026-03-18 21:51:17 +08:00
// 站点和管理员配置
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
}
// 校验 Redis 配置(启用时)
if req.RedisEnabled {
if strings.TrimSpace(req.RedisHost) == "" {
c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "启用 Redis 时主机地址不能为空"})
return
}
if req.RedisPort < 1 || req.RedisPort > 65535 {
c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "Redis 端口号无效"})
return
}
if req.RedisDB < 0 || req.RedisDB > 15 {
c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "Redis 数据库索引必须在 0-15 之间"})
return
}
}
2026-03-18 21:51:17 +08:00
// 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
}
// 写入 Redis 配置
if req.RedisEnabled {
cfg.Redis.Host = strings.TrimSpace(req.RedisHost)
cfg.Redis.Port = req.RedisPort
cfg.Redis.Password = req.RedisPassword
cfg.Redis.DB = req.RedisDB
} else {
if cfg.Redis.Host == "" {
cfg.Redis.Host = "localhost"
}
if cfg.Redis.Port == 0 {
cfg.Redis.Port = 6379
}
}
2026-03-18 21:51:17 +08:00
})
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()
2026-04-17 03:12:28 +08:00
database.SeedDefaultPortalNavigation()
2026-03-18 21:51:17 +08:00
// 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
2026-04-04 20:50:45 +08:00
if err := tx.Where("role = ?", 0).First(&adminUser).Error; err != nil {
2026-03-28 23:30:02 +08:00
// 如果不存在则创建
adminUser = models.User{
Username: strings.TrimSpace(req.AdminUsername),
Password: adminPasswordHash,
PasswordSalt: adminSalt,
2026-04-04 20:50:45 +08:00
Nickname: "超级管理员",
2026-03-28 23:30:02 +08:00
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
2026-04-04 20:50:45 +08:00
adminUser.Nickname = "超级管理员"
2026-03-28 23:30:02 +08:00
adminUser.Role = 0
if err := tx.Save(&adminUser).Error; err != nil {
tx.Rollback()
c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": "更新管理员账号失败"})
return
}
// 确保角色被更新为0GORM的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
// 6. 动态初始化核心组件
// 在系统安装完成后,执行本来在 server.go 中需要已安装才能执行的初始化逻辑
encryptionKey := services.GetSettingsService().GetEncryptionKey()
if err := utils.InitEncryption(encryptionKey); err != nil {
logrus.WithError(err).Error("安装完成后加密管理器初始化失败")
}
// 根据用户提供的 Redis 配置重新初始化 Redis 连接
if req.RedisEnabled {
utils.ReInitRedis()
if !utils.IsRedisAvailable() {
logrus.Warn("安装完成后 Redis 连接失败,请检查配置;系统将以无 Redis 模式运行")
}
}
// 启动日志清理定时任务
services.StartLogCleanupTask()
2026-03-18 21:51:17 +08:00
c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "安装成功"})
}