功能: - 前台导航: 分类Tab切换、实时搜索、健康状态指示、响应式适配 - 后台管理: 服务/分类CRUD、系统设置、登录认证(bcrypt) - 健康检查: 定时检测(5min)、独立检查URL、三态指示(在线/离线/未检测) - 云端备份: WebDAV上传/下载/恢复/删除、定时自动备份、本地备份管理 技术栈: Go + Gin + GORM + SQLite
120 lines
3.2 KiB
Go
120 lines
3.2 KiB
Go
package handlers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"html/template"
|
|
"net/http"
|
|
"tonav-go/database"
|
|
"tonav-go/models"
|
|
|
|
"github.com/gin-contrib/sessions"
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// AuthRequired 登录验证中间件
|
|
func AuthRequired() gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
session := sessions.Default(c)
|
|
userID := session.Get("user_id")
|
|
if userID == nil {
|
|
c.Redirect(http.StatusFound, "/admin/login")
|
|
c.Abort()
|
|
return
|
|
}
|
|
|
|
mustChange := session.Get("must_change")
|
|
if mustChange == true && c.Request.URL.Path != "/admin/change-password" && c.Request.URL.Path != "/admin/logout" {
|
|
c.Redirect(http.StatusFound, "/admin/change-password")
|
|
c.Abort()
|
|
return
|
|
}
|
|
c.Next()
|
|
}
|
|
}
|
|
|
|
// DashboardHandler 渲染后台首页
|
|
func DashboardHandler(c *gin.Context) {
|
|
var serviceCount, categoryCount int64
|
|
var onlineCount, offlineCount int64
|
|
database.DB.Model(&models.Service{}).Count(&serviceCount)
|
|
database.DB.Model(&models.Category{}).Count(&categoryCount)
|
|
database.DB.Model(&models.Service{}).Where("status = ?", "online").Count(&onlineCount)
|
|
database.DB.Model(&models.Service{}).Where("status = ?", "offline").Count(&offlineCount)
|
|
|
|
c.HTML(http.StatusOK, "dashboard.html", gin.H{
|
|
"service_count": serviceCount,
|
|
"category_count": categoryCount,
|
|
"online_count": onlineCount,
|
|
"offline_count": offlineCount,
|
|
})
|
|
}
|
|
|
|
// IndexHandler 渲染前台首页
|
|
func IndexHandler(c *gin.Context) {
|
|
if database.DB == nil {
|
|
c.String(http.StatusInternalServerError, "DB NIL")
|
|
return
|
|
}
|
|
|
|
// 获取所有分类
|
|
var categories []models.Category
|
|
database.DB.Order("sort_order desc").Find(&categories)
|
|
|
|
// 获取所有启用的服务
|
|
var services []models.Service
|
|
database.DB.Where("is_enabled = ?", true).Order("category_id asc, sort_order desc").Find(&services)
|
|
|
|
// 获取站点标题设置
|
|
var titleSetting models.Setting
|
|
siteTitle := "ToNav"
|
|
if err := database.DB.Where("key = ?", "site_title").First(&titleSetting).Error; err == nil && titleSetting.Value != "" {
|
|
siteTitle = titleSetting.Value
|
|
}
|
|
|
|
// 序列化为 JSON 供前端 JS 使用
|
|
categoriesJSON, _ := json.Marshal(categories)
|
|
servicesJSON, _ := json.Marshal(services)
|
|
|
|
c.HTML(http.StatusOK, "index.html", gin.H{
|
|
"site_title": siteTitle,
|
|
"categories": categories,
|
|
"categories_json": template.JS(categoriesJSON),
|
|
"services_json": template.JS(servicesJSON),
|
|
})
|
|
}
|
|
|
|
// ServicesPageHandler 渲染服务管理页面
|
|
func ServicesPageHandler(c *gin.Context) {
|
|
var categories []models.Category
|
|
database.DB.Order("sort_order desc").Find(&categories)
|
|
c.HTML(http.StatusOK, "services.html", gin.H{
|
|
"categories": categories,
|
|
})
|
|
}
|
|
|
|
// CategoriesPageHandler 渲染分类管理页面
|
|
func CategoriesPageHandler(c *gin.Context) {
|
|
c.HTML(http.StatusOK, "categories.html", nil)
|
|
}
|
|
|
|
// getSessionUserID 安全获取 session 中的 user_id
|
|
func getSessionUserID(session sessions.Session) (uint, error) {
|
|
userID := session.Get("user_id")
|
|
if userID == nil {
|
|
return 0, fmt.Errorf("user not logged in")
|
|
}
|
|
switch v := userID.(type) {
|
|
case uint:
|
|
return v, nil
|
|
case int:
|
|
return uint(v), nil
|
|
case int64:
|
|
return uint(v), nil
|
|
case float64:
|
|
return uint(v), nil
|
|
default:
|
|
return 0, fmt.Errorf("unexpected user_id type: %T", userID)
|
|
}
|
|
}
|