mirror of
https://github.com/skyle1995/NetworkAuth.git
synced 2026-05-25 02:24:05 +08:00
修复 导航栏重复初始化的问题
This commit is contained in:
4
build.sh
4
build.sh
@@ -54,9 +54,9 @@ build_backend() {
|
||||
local desc=$4
|
||||
|
||||
# 确定可执行文件名称
|
||||
local exe_name="NetworkAuth"
|
||||
local exe_name="ApiServe"
|
||||
if [ "$os" = "windows" ]; then
|
||||
exe_name="NetworkAuth.exe"
|
||||
exe_name="ApiServe.exe"
|
||||
fi
|
||||
|
||||
# 创建对应架构的输出目录
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"NetworkAuth/database"
|
||||
"NetworkAuth/middleware"
|
||||
"NetworkAuth/models"
|
||||
"NetworkAuth/server"
|
||||
"NetworkAuth/services"
|
||||
"NetworkAuth/utils"
|
||||
@@ -72,8 +73,19 @@ func runServer(cmd *cobra.Command, args []string) {
|
||||
|
||||
if db != nil {
|
||||
// 检查系统是否已安装
|
||||
isInstalled := services.GetSettingsService().GetString("is_installed", "0")
|
||||
if isInstalled == "1" {
|
||||
isInstalled := false
|
||||
if db.Migrator().HasTable(&models.Settings{}) {
|
||||
var setting models.Settings
|
||||
if err := db.Where("name = ?", "is_installed").First(&setting).Error; err == nil {
|
||||
isInstalled = setting.Value == "1"
|
||||
}
|
||||
}
|
||||
if isInstalled {
|
||||
needSeedPortalNavigation, err := database.NeedSeedDefaultPortalNavigation()
|
||||
if err != nil {
|
||||
logrus.WithError(err).Fatal("检测默认门户导航状态失败")
|
||||
}
|
||||
|
||||
// 执行自动迁移(确保表结构存在)
|
||||
if err := database.AutoMigrate(); err != nil {
|
||||
logrus.WithError(err).Fatal("数据库自动迁移失败")
|
||||
@@ -82,8 +94,10 @@ func runServer(cmd *cobra.Command, args []string) {
|
||||
if err := database.SeedDefaultSettings(); err != nil {
|
||||
logrus.WithError(err).Fatal("默认系统设置初始化失败")
|
||||
}
|
||||
if err := database.SeedDefaultPortalNavigation(); err != nil {
|
||||
logrus.WithError(err).Fatal("默认门户导航初始化失败")
|
||||
if needSeedPortalNavigation {
|
||||
if err := database.SeedDefaultPortalNavigation(); err != nil {
|
||||
logrus.WithError(err).Fatal("默认门户导航初始化失败")
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化加密管理器
|
||||
@@ -96,7 +110,7 @@ func runServer(cmd *cobra.Command, args []string) {
|
||||
// 启动日志清理定时任务
|
||||
services.StartLogCleanupTask()
|
||||
} else {
|
||||
logrus.Info("系统尚未安装 (is_installed=0),跳过核心组件初始化")
|
||||
logrus.Info("系统处于未安装状态,跳过数据库自动迁移和核心组件初始化")
|
||||
}
|
||||
} else {
|
||||
logrus.Info("系统处于未初始化状态,跳过数据库自动迁移和设置加载")
|
||||
|
||||
@@ -26,7 +26,7 @@ func PortalNavigationListHandler(c *gin.Context) {
|
||||
}
|
||||
|
||||
// PortalNavigationPublicListHandler 查询公开门户导航列表
|
||||
// 返回门户首页展示使用的可见导航数据
|
||||
// 返回门户首页展示使用的导航数据
|
||||
func PortalNavigationPublicListHandler(c *gin.Context) {
|
||||
db, ok := authBaseController.GetDB(c)
|
||||
if !ok {
|
||||
@@ -141,6 +141,18 @@ func PortalNavigationDeleteHandler(c *gin.Context) {
|
||||
authBaseController.HandleValidationError(c, "管理员登录导航为系统保留项,不允许删除")
|
||||
return
|
||||
}
|
||||
switch services.IsPortalNavigationGroup(item) {
|
||||
case true:
|
||||
var childCount int64
|
||||
if err := db.Model(&models.PortalNavigation{}).Where("parent_id = ?", item.ID).Count(&childCount).Error; err != nil {
|
||||
authBaseController.HandleInternalError(c, "查询分组子导航失败", err)
|
||||
return
|
||||
}
|
||||
if childCount > 0 {
|
||||
authBaseController.HandleValidationError(c, "请先移除分组下的导航链接")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := db.Delete(&item).Error; err != nil {
|
||||
authBaseController.HandleInternalError(c, "删除门户导航失败", err)
|
||||
@@ -155,6 +167,8 @@ func PortalNavigationDeleteHandler(c *gin.Context) {
|
||||
type portalNavigationPayload struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
ParentID uint `json:"parent_id"`
|
||||
Path string `json:"path"`
|
||||
Sort int `json:"sort"`
|
||||
IsHome bool `json:"is_home"`
|
||||
@@ -166,7 +180,10 @@ type portalNavigationPayload struct {
|
||||
// 负责统一做字段校验和数据转换
|
||||
func buildPortalNavigationFromPayload(c *gin.Context, body portalNavigationPayload) (models.PortalNavigation, bool) {
|
||||
item := models.PortalNavigation{
|
||||
ID: body.ID,
|
||||
Name: body.Name,
|
||||
Type: body.Type,
|
||||
ParentID: body.ParentID,
|
||||
Path: body.Path,
|
||||
Sort: body.Sort,
|
||||
IsHome: body.IsHome,
|
||||
@@ -175,7 +192,7 @@ func buildPortalNavigationFromPayload(c *gin.Context, body portalNavigationPaylo
|
||||
}
|
||||
services.NormalizePortalNavigation(&item)
|
||||
|
||||
if err := validatePortalNavigationInput(item); err != nil {
|
||||
if err := validatePortalNavigationInput(c, item); err != nil {
|
||||
authBaseController.HandleValidationError(c, err.Error())
|
||||
return models.PortalNavigation{}, false
|
||||
}
|
||||
@@ -185,21 +202,47 @@ func buildPortalNavigationFromPayload(c *gin.Context, body portalNavigationPaylo
|
||||
|
||||
// validatePortalNavigationInput 校验门户导航字段
|
||||
// 保证名称和地址满足基础格式要求
|
||||
func validatePortalNavigationInput(item models.PortalNavigation) error {
|
||||
func validatePortalNavigationInput(c *gin.Context, item models.PortalNavigation) error {
|
||||
switch {
|
||||
case item.Name == "":
|
||||
return fmt.Errorf("名称不能为空")
|
||||
case len(item.Name) > 64:
|
||||
return fmt.Errorf("名称长度不能超过64个字符")
|
||||
case item.Path == "":
|
||||
case item.Type != "link" && item.Type != "group":
|
||||
return fmt.Errorf("导航类型不合法")
|
||||
case item.Type == "link" && item.Path == "":
|
||||
return fmt.Errorf("地址不能为空")
|
||||
case len(item.Path) > 255:
|
||||
case item.Type == "link" && len(item.Path) > 255:
|
||||
return fmt.Errorf("地址长度不能超过255个字符")
|
||||
case item.Sort < 0:
|
||||
return fmt.Errorf("排序不能小于0")
|
||||
case item.IsHome && item.IsHidden:
|
||||
return fmt.Errorf("设为首页后禁止隐藏")
|
||||
case item.Type == "group" && item.ParentID > 0:
|
||||
return fmt.Errorf("分组不允许设置所属分组")
|
||||
case item.Type == "group" && item.IsHome:
|
||||
return fmt.Errorf("分组不允许设为首页")
|
||||
case item.Type == "group" && item.IsExternal:
|
||||
return fmt.Errorf("分组不支持外部打开")
|
||||
case item.ParentID > 0 && item.ParentID == item.ID:
|
||||
return fmt.Errorf("所属分组不能选择自身")
|
||||
case item.ParentID > 0 && item.IsHome:
|
||||
return fmt.Errorf("分组内链接不允许设为首页")
|
||||
default:
|
||||
db, ok := authBaseController.GetDB(c)
|
||||
if !ok {
|
||||
return fmt.Errorf("数据库连接失败")
|
||||
}
|
||||
if item.ParentID == 0 {
|
||||
return nil
|
||||
}
|
||||
var parent models.PortalNavigation
|
||||
if err := db.Where("id = ?", item.ParentID).First(&parent).Error; err != nil {
|
||||
return fmt.Errorf("所属分组不存在")
|
||||
}
|
||||
if !services.IsPortalNavigationGroup(parent) {
|
||||
return fmt.Errorf("所属导航不是分组")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,43 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// NeedSeedDefaultPortalNavigation 判断是否需要修复默认门户导航。
|
||||
// 仅在门户导航表缺失、关键字段缺失、没有任何数据或存在旧版脏数据时返回 true。
|
||||
func NeedSeedDefaultPortalNavigation() (bool, error) {
|
||||
db, err := GetDB()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if !db.Migrator().HasTable(&models.PortalNavigation{}) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
requiredColumns := []string{"type", "parent_id", "is_home", "is_hidden", "is_external"}
|
||||
for _, column := range requiredColumns {
|
||||
if !db.Migrator().HasColumn(&models.PortalNavigation{}, column) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
var count int64
|
||||
if err := db.Model(&models.PortalNavigation{}).Count(&count).Error; err != nil {
|
||||
return false, err
|
||||
}
|
||||
if count == 0 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if err := db.Model(&models.PortalNavigation{}).Where("type = '' OR type IS NULL").Count(&count).Error; err != nil {
|
||||
return false, err
|
||||
}
|
||||
if count > 0 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// SeedDefaultPortalNavigation 初始化默认门户导航
|
||||
// 当系统首次安装或升级后缺少默认入口时,自动补充首页和管理员登录入口
|
||||
func SeedDefaultPortalNavigation() error {
|
||||
@@ -19,6 +56,8 @@ func SeedDefaultPortalNavigation() error {
|
||||
defaultItems := []models.PortalNavigation{
|
||||
{
|
||||
Name: "首页",
|
||||
Type: "link",
|
||||
ParentID: 0,
|
||||
Path: "/home/index",
|
||||
Sort: 0,
|
||||
IsHome: true,
|
||||
@@ -27,6 +66,8 @@ func SeedDefaultPortalNavigation() error {
|
||||
},
|
||||
{
|
||||
Name: "管理员登录",
|
||||
Type: "link",
|
||||
ParentID: 0,
|
||||
Path: "admin",
|
||||
Sort: 999,
|
||||
IsHome: false,
|
||||
@@ -52,6 +93,8 @@ func SeedDefaultPortalNavigation() error {
|
||||
case true:
|
||||
if err := db.Model(&models.PortalNavigation{}).Where("id = ?", exists.ID).Updates(map[string]interface{}{
|
||||
"name": "管理员登录",
|
||||
"type": "link",
|
||||
"parent_id": 0,
|
||||
"path": "admin",
|
||||
"sort": 999,
|
||||
"is_home": false,
|
||||
@@ -65,6 +108,13 @@ func SeedDefaultPortalNavigation() error {
|
||||
}
|
||||
}
|
||||
|
||||
if err := db.Model(&models.PortalNavigation{}).Where("type = '' OR type IS NULL").Updates(map[string]interface{}{
|
||||
"type": "link",
|
||||
"parent_id": 0,
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logrus.Info("默认门户导航初始化完成")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ import "time"
|
||||
type PortalNavigation struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;comment:导航ID,自增主键"`
|
||||
Name string `json:"name" gorm:"size:64;not null;comment:导航名称"`
|
||||
Type string `json:"type" gorm:"size:16;not null;default:link;comment:导航类型,link=链接,group=分组"`
|
||||
ParentID uint `json:"parent_id" gorm:"default:0;not null;comment:所属分组ID,0表示顶级导航"`
|
||||
Path string `json:"path" gorm:"size:255;not null;comment:导航地址或路由路径"`
|
||||
Sort int `json:"sort" gorm:"default:0;not null;comment:排序值,越小越靠前,0最优先"`
|
||||
IsHome bool `json:"is_home" gorm:"default:false;comment:是否为门户首页"`
|
||||
|
||||
@@ -9,20 +9,43 @@ import (
|
||||
|
||||
const portalNavigationAdminPath = "admin"
|
||||
const portalNavigationAdminSort = 999
|
||||
const portalNavigationTypeLink = "link"
|
||||
const portalNavigationTypeGroup = "group"
|
||||
|
||||
// NormalizePortalNavigation 规范化门户导航数据
|
||||
// 统一清理首尾空白,并处理首页与排序约束
|
||||
// 统一清理首尾空白,避免保存脏数据
|
||||
func NormalizePortalNavigation(item *models.PortalNavigation) {
|
||||
item.Name = strings.TrimSpace(item.Name)
|
||||
item.Type = strings.ToLower(strings.TrimSpace(item.Type))
|
||||
if item.Type == "" {
|
||||
item.Type = portalNavigationTypeLink
|
||||
}
|
||||
item.Path = strings.TrimSpace(item.Path)
|
||||
if item.Sort < 0 {
|
||||
item.Sort = 0
|
||||
}
|
||||
if item.Type == portalNavigationTypeGroup {
|
||||
item.ParentID = 0
|
||||
item.Path = ""
|
||||
item.IsExternal = false
|
||||
item.IsHome = false
|
||||
}
|
||||
if item.IsHome {
|
||||
item.IsHidden = false
|
||||
item.ParentID = 0
|
||||
}
|
||||
}
|
||||
|
||||
// IsPortalNavigationGroup 判断是否为分组导航
|
||||
func IsPortalNavigationGroup(item models.PortalNavigation) bool {
|
||||
return strings.EqualFold(strings.TrimSpace(item.Type), portalNavigationTypeGroup)
|
||||
}
|
||||
|
||||
// IsPortalNavigationLink 判断是否为链接导航
|
||||
func IsPortalNavigationLink(item models.PortalNavigation) bool {
|
||||
return !IsPortalNavigationGroup(item)
|
||||
}
|
||||
|
||||
// IsPortalNavigationAdminEntry 判断是否为管理员入口
|
||||
// 管理员入口属于系统保留导航项,不允许修改基础信息
|
||||
func IsPortalNavigationAdminEntry(item models.PortalNavigation) bool {
|
||||
@@ -30,11 +53,13 @@ func IsPortalNavigationAdminEntry(item models.PortalNavigation) bool {
|
||||
}
|
||||
|
||||
// LockPortalNavigationProtectedFields 锁定系统保留导航字段
|
||||
// 管理员入口仅允许调整隐藏状态,其余字段保持系统固定值
|
||||
// 管理员入口仅允许调整隐藏状态,其余字段保持数据库原值
|
||||
func LockPortalNavigationProtectedFields(item *models.PortalNavigation, exists models.PortalNavigation) {
|
||||
switch IsPortalNavigationAdminEntry(exists) {
|
||||
case true:
|
||||
item.Name = "管理员登录"
|
||||
item.Type = portalNavigationTypeLink
|
||||
item.ParentID = 0
|
||||
item.Path = portalNavigationAdminPath
|
||||
item.Sort = portalNavigationAdminSort
|
||||
item.IsHome = false
|
||||
@@ -69,6 +94,8 @@ func SavePortalNavigation(db *gorm.DB, item *models.PortalNavigation, exists ...
|
||||
default:
|
||||
return tx.Model(&models.PortalNavigation{}).Where("id = ?", item.ID).Updates(map[string]interface{}{
|
||||
"name": item.Name,
|
||||
"type": item.Type,
|
||||
"parent_id": item.ParentID,
|
||||
"path": item.Path,
|
||||
"sort": item.Sort,
|
||||
"is_home": item.IsHome,
|
||||
|
||||
Reference in New Issue
Block a user