mirror of
https://github.com/skyle1995/NetworkAuth.git
synced 2026-05-25 02:24:05 +08:00
更新底层架构
This commit is contained in:
@@ -1,15 +1,19 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"NetworkAuth/utils"
|
||||
"fmt"
|
||||
"networkDev/utils"
|
||||
"log"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/viper"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/gorm"
|
||||
gLogger "gorm.io/gorm/logger"
|
||||
)
|
||||
|
||||
// ============================================================================
|
||||
@@ -33,40 +37,7 @@ var (
|
||||
func Init() (*gorm.DB, error) {
|
||||
var initErr error
|
||||
once.Do(func() {
|
||||
dbType := viper.GetString("database.type")
|
||||
switch dbType {
|
||||
case "mysql":
|
||||
initErr = initMySQL()
|
||||
default:
|
||||
initErr = initSQLite()
|
||||
}
|
||||
|
||||
// 如果数据库初始化成功,配置连接池和启动健康检查
|
||||
if initErr == nil && dbInstance != nil {
|
||||
// 加载数据库配置
|
||||
var configPrefix string
|
||||
if dbType == "mysql" {
|
||||
configPrefix = "database.mysql"
|
||||
} else {
|
||||
configPrefix = "database.sqlite"
|
||||
}
|
||||
|
||||
dbConfig := utils.LoadDatabaseConfig(configPrefix)
|
||||
|
||||
// 验证配置
|
||||
if err := utils.ValidateDatabaseConfig(dbConfig); err != nil {
|
||||
logrus.WithError(err).Warn("数据库配置验证失败,使用默认配置")
|
||||
dbConfig = utils.GetDefaultDatabaseConfig()
|
||||
}
|
||||
|
||||
// 配置连接池
|
||||
if err := utils.ConfigureConnectionPool(dbInstance, dbConfig); err != nil {
|
||||
logrus.WithError(err).Error("配置数据库连接池失败")
|
||||
}
|
||||
|
||||
// 启动健康检查
|
||||
utils.StartHealthCheck(dbInstance, dbConfig)
|
||||
}
|
||||
initErr = performInit()
|
||||
})
|
||||
return dbInstance, initErr
|
||||
}
|
||||
@@ -80,6 +51,89 @@ func GetDB() (*gorm.DB, error) {
|
||||
return Init()
|
||||
}
|
||||
|
||||
// ReInit 重新初始化数据库连接
|
||||
// 用于在修改配置后重新连接数据库
|
||||
func ReInit() (*gorm.DB, error) {
|
||||
// 如果已有连接,尝试关闭它
|
||||
if dbInstance != nil {
|
||||
if sqlDB, err := dbInstance.DB(); err == nil {
|
||||
sqlDB.Close()
|
||||
}
|
||||
}
|
||||
dbInstance = nil
|
||||
|
||||
// 重新执行初始化逻辑(不经过 once.Do)
|
||||
return dbInstance, performInit()
|
||||
}
|
||||
|
||||
func performInit() error {
|
||||
// 检查是否已经有配置文件(通过检查文件是否存在)
|
||||
configFile := viper.ConfigFileUsed()
|
||||
// 如果 viper 没有使用配置文件(可能是因为没找到文件而使用了默认配置),
|
||||
// 或者配置文件路径为空,我们应该假设处于未安装状态。
|
||||
// 但 viper.ConfigFileUsed() 在 ReadInConfig 成功后会返回文件名。
|
||||
// 如果 ReadInConfig 失败(因为文件不存在),viper 可能会返回空或者我们在 config.go 中设置的路径。
|
||||
|
||||
// 在 config.go 中,如果文件不存在,我们加载了默认配置但没有写文件。
|
||||
// 此时 viper.ConfigFileUsed() 可能是空的或者我们设置的路径。
|
||||
// 让我们检查该路径对应的文件是否存在。
|
||||
|
||||
if configFile == "" {
|
||||
configFile = "config.json"
|
||||
}
|
||||
|
||||
_, err := os.Stat(configFile)
|
||||
isConfigExists := !os.IsNotExist(err)
|
||||
|
||||
// 如果配置文件不存在,说明还没有经过安装初始化,暂时不连接数据库
|
||||
if !isConfigExists {
|
||||
logrus.Info("尚未初始化配置,跳过数据库连接")
|
||||
return nil
|
||||
}
|
||||
|
||||
var initErr error
|
||||
dbType := viper.GetString("database.type")
|
||||
switch dbType {
|
||||
case "mysql":
|
||||
initErr = initMySQL()
|
||||
default:
|
||||
initErr = initSQLite()
|
||||
}
|
||||
|
||||
// 如果数据库初始化成功,配置连接池和启动健康检查
|
||||
if initErr == nil && dbInstance != nil {
|
||||
// 加载数据库配置
|
||||
var configPrefix string
|
||||
if dbType == "mysql" {
|
||||
configPrefix = "database.mysql"
|
||||
} else {
|
||||
configPrefix = "database.sqlite"
|
||||
}
|
||||
|
||||
dbConfig := utils.LoadDatabaseConfig(configPrefix)
|
||||
|
||||
// 验证配置
|
||||
if err := utils.ValidateDatabaseConfig(dbConfig); err != nil {
|
||||
logrus.WithError(err).Warn("数据库配置验证失败,使用默认配置")
|
||||
dbConfig = utils.GetDefaultDatabaseConfig()
|
||||
}
|
||||
|
||||
// 配置连接池
|
||||
if err := utils.ConfigureConnectionPool(dbInstance, dbConfig); err != nil {
|
||||
logrus.WithError(err).Error("配置数据库连接池失败")
|
||||
}
|
||||
|
||||
// 启动健康检查
|
||||
utils.StartHealthCheck(dbInstance, dbConfig)
|
||||
}
|
||||
return initErr
|
||||
}
|
||||
|
||||
// SetDB 设置全局 *gorm.DB 实例(用于测试)
|
||||
func SetDB(db *gorm.DB) {
|
||||
dbInstance = db
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 私有函数
|
||||
// ============================================================================
|
||||
@@ -89,10 +143,20 @@ func GetDB() (*gorm.DB, error) {
|
||||
func initSQLite() error {
|
||||
path := viper.GetString("database.sqlite.path")
|
||||
if path == "" {
|
||||
path = "./database.db"
|
||||
path = "./recharge.db"
|
||||
}
|
||||
dsn := fmt.Sprintf("file:%s?cache=shared&_busy_timeout=5000&_fk=1", path)
|
||||
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
|
||||
var logLevel gLogger.LogLevel
|
||||
switch viper.GetString("logger.level") {
|
||||
case "debug":
|
||||
logLevel = gLogger.Info
|
||||
case "error":
|
||||
logLevel = gLogger.Error
|
||||
default:
|
||||
logLevel = gLogger.Warn
|
||||
}
|
||||
gl := gLogger.New(log.New(os.Stdout, "\r\n", log.LstdFlags), gLogger.Config{SlowThreshold: 2 * time.Second, LogLevel: logLevel, IgnoreRecordNotFoundError: true, Colorful: false})
|
||||
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{Logger: gl})
|
||||
if err != nil {
|
||||
logrus.WithError(err).Error("SQLite 初始化失败")
|
||||
return err
|
||||
@@ -124,7 +188,17 @@ func initMySQL() error {
|
||||
}
|
||||
|
||||
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s&parseTime=True&loc=Local", user, pass, host, port, dbname, charset)
|
||||
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
|
||||
var logLevel gLogger.LogLevel
|
||||
switch viper.GetString("logger.level") {
|
||||
case "debug":
|
||||
logLevel = gLogger.Info
|
||||
case "error":
|
||||
logLevel = gLogger.Error
|
||||
default:
|
||||
logLevel = gLogger.Warn
|
||||
}
|
||||
gl := gLogger.New(log.New(os.Stdout, "\r\n", log.LstdFlags), gLogger.Config{SlowThreshold: 2 * time.Second, LogLevel: logLevel, IgnoreRecordNotFoundError: true, Colorful: false})
|
||||
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{Logger: gl})
|
||||
if err != nil {
|
||||
logrus.WithError(err).Error("MySQL 初始化失败")
|
||||
return err
|
||||
|
||||
@@ -1,180 +1,32 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"networkDev/models"
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ============================================================================
|
||||
// 公共函数
|
||||
// ============================================================================
|
||||
|
||||
// AutoMigrate 自动迁移数据库模型
|
||||
// - 会确保必要的数据表结构存在
|
||||
// - 不会破坏已有数据
|
||||
func AutoMigrate() error {
|
||||
db, err := GetDB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := db.AutoMigrate(&models.User{}, &models.Settings{}, &models.App{}, &models.API{}, &models.Variable{}, &models.Function{}); err != nil {
|
||||
logrus.WithError(err).Error("AutoMigrate 执行失败")
|
||||
return err
|
||||
}
|
||||
|
||||
// 兼容迁移:如果 users.password_salt 列长度 < 64,则扩大到 64
|
||||
if err := ensureUserPasswordSaltLength(db); err != nil {
|
||||
logrus.WithError(err).Error("调整 users.password_salt 列长度失败")
|
||||
return err
|
||||
}
|
||||
|
||||
// 兼容迁移:确保 tasks.verification_code 字段类型为 LONGTEXT 以支持大图片数据
|
||||
if err := ensureVerificationCodeType(db); err != nil {
|
||||
logrus.WithError(err).Error("调整 tasks.verification_code 字段类型失败")
|
||||
return err
|
||||
}
|
||||
|
||||
logrus.Info("AutoMigrate 执行完成")
|
||||
return nil
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 私有函数
|
||||
// ============================================================================
|
||||
|
||||
// ensureVerificationCodeType 确保tasks.verification_code字段类型为LONGTEXT以支持大图片数据
|
||||
// 中文注释:检查并修改verification_code字段类型,支持Base64编码的大图片数据存储
|
||||
func ensureVerificationCodeType(db *gorm.DB) error {
|
||||
// 获取数据库方言类型
|
||||
dialector := db.Dialector.Name()
|
||||
|
||||
// 根据不同数据库类型执行不同的检查逻辑
|
||||
switch dialector {
|
||||
case "mysql":
|
||||
// MySQL/MariaDB使用INFORMATION_SCHEMA
|
||||
var result struct {
|
||||
ColumnName string `gorm:"column:COLUMN_NAME"`
|
||||
ColumnType string `gorm:"column:COLUMN_TYPE"`
|
||||
}
|
||||
|
||||
err := db.Raw("SELECT COLUMN_NAME, COLUMN_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = ? AND COLUMN_NAME = ? LIMIT 1",
|
||||
"tasks", "verification_code").Scan(&result).Error
|
||||
|
||||
if err != nil {
|
||||
return nil // 查询失败则跳过
|
||||
}
|
||||
|
||||
// 检查列类型,如果不是LONGTEXT则修改
|
||||
if !strings.Contains(strings.ToLower(result.ColumnType), "longtext") {
|
||||
alterSQL := "ALTER TABLE tasks MODIFY COLUMN verification_code LONGTEXT"
|
||||
if err := db.Exec(alterSQL).Error; err != nil {
|
||||
return fmt.Errorf("修改verification_code字段类型失败: %v", err)
|
||||
}
|
||||
logrus.Info("verification_code字段类型已更新为LONGTEXT")
|
||||
}
|
||||
case "sqlite":
|
||||
// SQLite使用pragma_table_info检查列信息
|
||||
var columns []struct {
|
||||
CID int `gorm:"column:cid"`
|
||||
Name string `gorm:"column:name"`
|
||||
Type string `gorm:"column:type"`
|
||||
NotNull int `gorm:"column:notnull"`
|
||||
DfltValue *string `gorm:"column:dflt_value"`
|
||||
PK int `gorm:"column:pk"`
|
||||
}
|
||||
|
||||
err := db.Raw("PRAGMA table_info(tasks)").Scan(&columns).Error
|
||||
if err != nil {
|
||||
return nil // 查询失败则跳过
|
||||
}
|
||||
|
||||
// 查找verification_code列
|
||||
for _, col := range columns {
|
||||
if col.Name == "verification_code" {
|
||||
// SQLite中,如果列类型不是TEXT,需要重建表
|
||||
if !strings.Contains(strings.ToLower(col.Type), "text") {
|
||||
// SQLite不支持直接修改列类型,但GORM的AutoMigrate会处理这种情况
|
||||
logrus.Info("SQLite检测到verification_code字段类型需要更新,依赖GORM AutoMigrate处理")
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
default:
|
||||
// 其他数据库类型暂不处理
|
||||
logrus.Infof("数据库类型 %s 暂不支持verification_code字段类型检查", dialector)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ensureUserPasswordSaltLength 确保users.password_salt列长度至少为64
|
||||
// 中文注释:检查并修改password_salt列长度,兼容32字节(64十六进制字符)的盐值
|
||||
func ensureUserPasswordSaltLength(db *gorm.DB) error {
|
||||
// 获取数据库方言类型
|
||||
dialector := db.Dialector.Name()
|
||||
|
||||
// 根据不同数据库类型执行不同的检查逻辑
|
||||
switch dialector {
|
||||
case "mysql":
|
||||
// MySQL/MariaDB使用INFORMATION_SCHEMA
|
||||
var result struct {
|
||||
ColumnName string `gorm:"column:COLUMN_NAME"`
|
||||
ColumnType string `gorm:"column:COLUMN_TYPE"`
|
||||
}
|
||||
|
||||
err := db.Raw("SELECT COLUMN_NAME, COLUMN_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = ? AND COLUMN_NAME = ? LIMIT 1",
|
||||
"users", "password_salt").Scan(&result).Error
|
||||
|
||||
if err != nil {
|
||||
return nil // 查询失败则跳过
|
||||
}
|
||||
|
||||
// 检查列类型,如果长度小于64则修改
|
||||
if strings.Contains(strings.ToLower(result.ColumnType), "varchar") {
|
||||
if strings.Contains(result.ColumnType, "(32)") || strings.Contains(result.ColumnType, "(16)") {
|
||||
alterSQL := "ALTER TABLE users MODIFY COLUMN password_salt VARCHAR(64)"
|
||||
if err := db.Exec(alterSQL).Error; err != nil {
|
||||
return fmt.Errorf("修改password_salt列长度失败: %v", err)
|
||||
}
|
||||
logrus.Info("password_salt列长度已更新为64")
|
||||
}
|
||||
}
|
||||
case "sqlite":
|
||||
// SQLite使用pragma_table_info检查列信息
|
||||
var columns []struct {
|
||||
CID int `gorm:"column:cid"`
|
||||
Name string `gorm:"column:name"`
|
||||
Type string `gorm:"column:type"`
|
||||
NotNull int `gorm:"column:notnull"`
|
||||
DfltValue *string `gorm:"column:dflt_value"`
|
||||
PK int `gorm:"column:pk"`
|
||||
}
|
||||
|
||||
err := db.Raw("PRAGMA table_info(users)").Scan(&columns).Error
|
||||
if err != nil {
|
||||
return nil // 查询失败则跳过
|
||||
}
|
||||
|
||||
// 查找password_salt列
|
||||
for _, col := range columns {
|
||||
if col.Name == "password_salt" {
|
||||
// SQLite中,如果列类型包含长度限制且小于64,需要重建表
|
||||
if strings.Contains(strings.ToLower(col.Type), "varchar(32)") ||
|
||||
strings.Contains(strings.ToLower(col.Type), "varchar(16)") {
|
||||
// SQLite不支持直接修改列类型,但GORM的AutoMigrate会处理这种情况
|
||||
logrus.Info("SQLite检测到password_salt列长度需要更新,依赖GORM AutoMigrate处理")
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
default:
|
||||
// 其他数据库类型暂不处理
|
||||
logrus.Infof("数据库类型 %s 暂不支持password_salt列长度检查", dialector)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
package database
|
||||
|
||||
import (
|
||||
"NetworkAuth/models"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// AutoMigrate 自动迁移数据库模型
|
||||
// - 会确保必要的数据表结构存在
|
||||
// - 不会破坏已有数据
|
||||
func AutoMigrate() error {
|
||||
db, err := GetDB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := db.AutoMigrate(
|
||||
&models.Settings{},
|
||||
&models.OperationLog{},
|
||||
&models.LoginLog{},
|
||||
&models.App{},
|
||||
&models.API{},
|
||||
&models.Function{},
|
||||
&models.Variable{},
|
||||
&models.User{},
|
||||
); err != nil {
|
||||
logrus.WithError(err).Error("AutoMigrate 执行失败")
|
||||
return err
|
||||
}
|
||||
logrus.Info("AutoMigrate 执行完成")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,186 +1,232 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"networkDev/models"
|
||||
"networkDev/utils"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ============================================================================
|
||||
// 公共函数
|
||||
// ============================================================================
|
||||
|
||||
// SeedDefaultSettings 初始化默认系统设置
|
||||
// - 检查各项设置是否已存在,如不存在则创建默认值
|
||||
// - 包含站点基本信息、SEO设置等常用配置项
|
||||
func SeedDefaultSettings() error {
|
||||
db, err := GetDB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 定义默认设置项
|
||||
defaultSettings := []models.Settings{
|
||||
{
|
||||
Name: "site_title",
|
||||
Value: "凌动技术",
|
||||
Description: "网站标题,显示在浏览器标题栏和页面顶部",
|
||||
},
|
||||
{
|
||||
Name: "site_keywords",
|
||||
Value: "验证,网络,管理系统,网络验证,卡密管理,账户管理",
|
||||
Description: "网站关键词,用于SEO优化,多个关键词用逗号分隔",
|
||||
},
|
||||
{
|
||||
Name: "site_description",
|
||||
Value: "专业的网络验证管理系统,提供便捷的在线网络验证服务和设备管理功能",
|
||||
Description: "网站描述,用于SEO优化和社交媒体分享",
|
||||
},
|
||||
{
|
||||
Name: "site_logo",
|
||||
Value: "/favicon.ico",
|
||||
Description: "网站Logo图片路径",
|
||||
},
|
||||
{
|
||||
Name: "contact_email",
|
||||
Value: "admin@example.com",
|
||||
Description: "联系邮箱,用于客服和业务咨询",
|
||||
},
|
||||
{
|
||||
Name: "max_upload_size",
|
||||
Value: "10485760",
|
||||
Description: "文件上传最大尺寸(字节),默认10MB",
|
||||
},
|
||||
{
|
||||
Name: "default_user_role",
|
||||
Value: "1",
|
||||
Description: "新用户默认角色,0=管理员,1=普通用户",
|
||||
},
|
||||
{
|
||||
Name: "session_timeout",
|
||||
Value: "3600",
|
||||
Description: "会话超时时间(秒),默认1小时",
|
||||
},
|
||||
{
|
||||
Name: "maintenance_mode",
|
||||
Value: "0",
|
||||
Description: "维护模式,0=关闭维护模式,1=开启维护模式",
|
||||
},
|
||||
// ===== 管理员账号相关默认项 =====
|
||||
{
|
||||
Name: "admin_username",
|
||||
Value: "admin",
|
||||
Description: "管理员用户名",
|
||||
},
|
||||
{
|
||||
Name: "admin_password",
|
||||
Value: "",
|
||||
Description: "管理员密码哈希值",
|
||||
},
|
||||
{
|
||||
Name: "admin_password_salt",
|
||||
Value: "",
|
||||
Description: "管理员密码加密盐值",
|
||||
},
|
||||
// ===== 页脚与备案相关默认项 =====
|
||||
{
|
||||
Name: "footer_text",
|
||||
Value: "Copyright © 2025 凌动技术. All Rights Reserved.",
|
||||
Description: "页脚展示的版权或说明信息",
|
||||
},
|
||||
{
|
||||
Name: "icp_record",
|
||||
Value: "京ICP备12345678号",
|
||||
Description: "ICP备案号,留空则不显示",
|
||||
},
|
||||
{
|
||||
Name: "icp_record_link",
|
||||
Value: "https://beian.miit.gov.cn",
|
||||
Description: "工信部ICP备案查询链接,留空则不显示",
|
||||
},
|
||||
{
|
||||
Name: "psb_record",
|
||||
Value: "京公网安备 11000002000001号",
|
||||
Description: "公安备案号,留空则不显示",
|
||||
},
|
||||
{
|
||||
Name: "psb_record_link",
|
||||
Value: "https://www.beian.gov.cn/portal/registerSystemInfo?recordcode=11000002000001",
|
||||
Description: "公安备案查询链接,留空则不显示",
|
||||
},
|
||||
}
|
||||
|
||||
// 逐个检查并创建不存在的设置项
|
||||
for _, setting := range defaultSettings {
|
||||
var count int64
|
||||
if err := db.Model(&models.Settings{}).Where("name = ?", setting.Name).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
if err := db.Create(&setting).Error; err != nil {
|
||||
logrus.WithError(err).WithField("name", setting.Name).Error("创建默认设置失败")
|
||||
return err
|
||||
}
|
||||
logrus.WithField("name", setting.Name).WithField("value", setting.Value).Debug("创建默认设置项")
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化默认管理员账号(如果密码为空)
|
||||
if err := initDefaultAdmin(db); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logrus.Info("默认系统设置初始化完成")
|
||||
return nil
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 私有函数
|
||||
// ============================================================================
|
||||
|
||||
// initDefaultAdmin 初始化默认管理员账号
|
||||
// 如果admin_password为空,则生成默认密码admin123的哈希值
|
||||
func initDefaultAdmin(db *gorm.DB) error {
|
||||
var passwordSetting models.Settings
|
||||
if err := db.Where("name = ?", "admin_password").First(&passwordSetting).Error; err != nil {
|
||||
logrus.WithError(err).Error("获取管理员密码设置失败")
|
||||
return err
|
||||
}
|
||||
|
||||
// 如果密码已设置,跳过初始化
|
||||
if passwordSetting.Value != "" {
|
||||
logrus.Debug("管理员密码已设置,跳过默认密码初始化")
|
||||
return nil
|
||||
}
|
||||
|
||||
// 生成密码盐值
|
||||
salt, err := utils.GenerateRandomSalt()
|
||||
if err != nil {
|
||||
logrus.WithError(err).Error("生成密码盐值失败")
|
||||
return err
|
||||
}
|
||||
|
||||
// 使用盐值生成密码哈希(默认密码:admin123)
|
||||
hash, err := utils.HashPasswordWithSalt("admin123", salt)
|
||||
if err != nil {
|
||||
logrus.WithError(err).Error("生成密码哈希失败")
|
||||
return err
|
||||
}
|
||||
|
||||
// 更新密码和盐值
|
||||
if err := db.Model(&models.Settings{}).Where("name = ?", "admin_password").Update("value", hash).Error; err != nil {
|
||||
logrus.WithError(err).Error("更新管理员密码失败")
|
||||
return err
|
||||
}
|
||||
|
||||
if err := db.Model(&models.Settings{}).Where("name = ?", "admin_password_salt").Update("value", salt).Error; err != nil {
|
||||
logrus.WithError(err).Error("更新管理员密码盐值失败")
|
||||
return err
|
||||
}
|
||||
|
||||
logrus.Info("默认管理员账号初始化完成,用户名: admin, 密码: admin123")
|
||||
return nil
|
||||
}
|
||||
package database
|
||||
|
||||
import (
|
||||
"NetworkAuth/config"
|
||||
"NetworkAuth/models"
|
||||
"NetworkAuth/utils"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// ============================================================================
|
||||
// 公共函数
|
||||
// ============================================================================
|
||||
|
||||
// SeedDefaultSettings 初始化默认系统设置
|
||||
// - 检查各项设置是否已存在,如不存在则创建默认值
|
||||
// - 包含站点基本信息、SEO设置等常用配置项
|
||||
func SeedDefaultSettings() error {
|
||||
db, err := GetDB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 生成安全的随机密钥
|
||||
jwtSecret, err := config.GenerateSecureJWTSecret()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
encryptionKey, err := config.GenerateSecureEncryptionKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 生成默认管理员密码(admin123)的盐值和哈希
|
||||
// 这样可以确保admin_password和admin_password_salt在初始化时就有值
|
||||
adminSalt, err := utils.GenerateRandomSalt()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
adminPasswordHash, err := utils.HashPasswordWithSalt("admin123", adminSalt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 检查是否已有 admin_password,如果有,说明是旧版本升级,应该把 is_installed 默认设为 1
|
||||
var adminPwdCount int64
|
||||
db.Model(&models.Settings{}).Where("name = ?", "admin_password").Count(&adminPwdCount)
|
||||
isInstalledDefault := "0"
|
||||
if adminPwdCount > 0 {
|
||||
isInstalledDefault = "1"
|
||||
}
|
||||
|
||||
// 定义默认设置项
|
||||
defaultSettings := []models.Settings{
|
||||
// ===== 系统安装状态 =====
|
||||
{
|
||||
Name: "is_installed",
|
||||
Value: isInstalledDefault,
|
||||
Description: "系统是否已初始化安装,0=未安装,1=已安装",
|
||||
},
|
||||
// ===== 管理员账号相关默认项 =====
|
||||
{
|
||||
Name: "admin_username",
|
||||
Value: "admin",
|
||||
Description: "管理员用户名",
|
||||
},
|
||||
{
|
||||
Name: "admin_password",
|
||||
Value: adminPasswordHash,
|
||||
Description: "管理员密码哈希值",
|
||||
},
|
||||
{
|
||||
Name: "admin_password_salt",
|
||||
Value: adminSalt,
|
||||
Description: "管理员密码加密盐值",
|
||||
},
|
||||
// ===== 系统和安全相关默认项 =====
|
||||
{
|
||||
Name: "maintenance_mode",
|
||||
Value: "0",
|
||||
Description: "维护模式,0=关闭维护模式,1=开启维护模式",
|
||||
},
|
||||
{
|
||||
Name: "encryption_key",
|
||||
Value: encryptionKey,
|
||||
Description: "数据加密密钥",
|
||||
},
|
||||
{
|
||||
Name: "jwt_secret",
|
||||
Value: jwtSecret,
|
||||
Description: "JWT签名密钥",
|
||||
},
|
||||
{
|
||||
Name: "jwt_refresh",
|
||||
Value: "6",
|
||||
Description: "JWT令牌刷新阈值(小时)",
|
||||
},
|
||||
{
|
||||
Name: "jwt_expire",
|
||||
Value: "24",
|
||||
Description: "JWT令牌有效期(小时)",
|
||||
},
|
||||
{
|
||||
Name: "session_timeout",
|
||||
Value: "3600",
|
||||
Description: "会话超时时间(秒),默认1小时",
|
||||
},
|
||||
{
|
||||
Name: "max_upload_size",
|
||||
Value: "10485760",
|
||||
Description: "文件上传最大尺寸(字节),默认10MB",
|
||||
},
|
||||
{
|
||||
Name: "default_user_role",
|
||||
Value: "1",
|
||||
Description: "新用户默认角色,0=管理员,1=普通用户",
|
||||
},
|
||||
// ===== 日志清理策略默认项 =====
|
||||
{
|
||||
Name: "login_log_cleanup_days",
|
||||
Value: "30",
|
||||
Description: "登录日志保留天数(0表示不按天清理)",
|
||||
},
|
||||
{
|
||||
Name: "login_log_cleanup_limit",
|
||||
Value: "10000",
|
||||
Description: "登录日志保留条数(0表示不按数量清理)",
|
||||
},
|
||||
{
|
||||
Name: "operation_log_cleanup_days",
|
||||
Value: "30",
|
||||
Description: "操作日志保留天数(0表示不按天清理)",
|
||||
},
|
||||
{
|
||||
Name: "operation_log_cleanup_limit",
|
||||
Value: "10000",
|
||||
Description: "操作日志保留条数(0表示不按数量清理)",
|
||||
},
|
||||
// ===== Cookie相关默认项 =====
|
||||
{
|
||||
Name: "cookie_secure",
|
||||
Value: "true",
|
||||
Description: "Cookie Secure属性(是否只在HTTPS下发送)",
|
||||
},
|
||||
{
|
||||
Name: "cookie_same_site",
|
||||
Value: "Lax",
|
||||
Description: "Cookie SameSite属性(Strict/Lax/None)",
|
||||
},
|
||||
{
|
||||
Name: "cookie_domain",
|
||||
Value: "",
|
||||
Description: "Cookie域名",
|
||||
},
|
||||
{
|
||||
Name: "cookie_max_age",
|
||||
Value: "86400",
|
||||
Description: "Cookie最大存活时间(秒)",
|
||||
},
|
||||
// ===== 站点基本信息默认项 =====
|
||||
{
|
||||
Name: "site_title",
|
||||
Value: "NetworkAuth",
|
||||
Description: "网站标题,显示在浏览器标题栏和页面顶部",
|
||||
},
|
||||
{
|
||||
Name: "site_keywords",
|
||||
Value: "NetworkAuth,鉴权,API管理,GoLang",
|
||||
Description: "网站关键词,用于SEO优化,多个关键词用逗号分隔",
|
||||
},
|
||||
{
|
||||
Name: "site_description",
|
||||
Value: "NetworkAuth 网络授权服务,专注于应用鉴权与接口管理",
|
||||
Description: "网站描述,用于SEO优化和社交媒体分享",
|
||||
},
|
||||
{
|
||||
Name: "site_logo",
|
||||
Value: "/static/logo.png",
|
||||
Description: "网站Logo图片路径",
|
||||
},
|
||||
{
|
||||
Name: "contact_email",
|
||||
Value: "admin@example.com",
|
||||
Description: "联系邮箱,用于客服和业务咨询",
|
||||
},
|
||||
// ===== 页脚与备案相关默认项 =====
|
||||
{
|
||||
Name: "footer_text",
|
||||
Value: "Copyright © 2026 NetworkAuth. All Rights Reserved.",
|
||||
Description: "页脚展示的版权或说明信息",
|
||||
},
|
||||
{
|
||||
Name: "icp_record",
|
||||
Value: "",
|
||||
Description: "ICP备案号,留空则不显示",
|
||||
},
|
||||
{
|
||||
Name: "icp_record_link",
|
||||
Value: "https://beian.miit.gov.cn",
|
||||
Description: "工信部ICP备案查询链接,留空则不显示",
|
||||
},
|
||||
{
|
||||
Name: "psb_record",
|
||||
Value: "",
|
||||
Description: "公安备案号,留空则不显示",
|
||||
},
|
||||
{
|
||||
Name: "psb_record_link",
|
||||
Value: "",
|
||||
Description: "公安备案查询链接,留空则不显示",
|
||||
},
|
||||
}
|
||||
|
||||
// 逐个检查并创建不存在的设置项
|
||||
for _, setting := range defaultSettings {
|
||||
var count int64
|
||||
if err := db.Model(&models.Settings{}).Where("name = ?", setting.Name).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
if err := db.Create(&setting).Error; err != nil {
|
||||
logrus.WithError(err).WithField("name", setting.Name).Error("创建默认设置失败")
|
||||
return err
|
||||
}
|
||||
logrus.WithField("name", setting.Name).WithField("value", setting.Value).Debug("创建默认设置项")
|
||||
}
|
||||
}
|
||||
|
||||
logrus.Info("默认系统设置初始化完成")
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user