Files
portal_page/main.go
T
kevin 2f3d47a439 feat: TCP 和 Unix socket 同时监听
之前是二选一(配了 unix 就不监听 TCP),调试不方便。
现在 TCP 始终监听(调试/健康检查),Unix socket 配了就额外监听(nginx 反代)。
启动时清理残留 socket 文件,避免 bind 失败。
2026-05-28 15:59:38 +08:00

194 lines
5.2 KiB
Go

package main
import (
"html/template"
"log"
"net"
"os"
"path/filepath"
"strings"
"sync"
"simple_portal/config"
"simple_portal/database"
"simple_portal/handlers"
"simple_portal/middleware"
"simple_portal/session"
"github.com/gin-gonic/gin"
)
// loadTemplates 加载 templates/ 目录下所有 HTML 模板
// 自定义实现,因为 Go 的 ParseGlob 在 Windows 下有路径问题
func loadTemplates() *template.Template {
funcMap := template.FuncMap{
"hasPrefix": strings.HasPrefix,
"sub": func(a, b int) int { return a - b },
"add": func(a, b int) int { return a + b },
}
t := template.New("").Funcs(funcMap)
var files []string
filepath.Walk("templates", func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() && filepath.Ext(path) == ".html" {
files = append(files, path)
}
return nil
})
if len(files) == 0 {
log.Fatal("No template files found in templates/")
}
for i, f := range files {
files[i] = filepath.ToSlash(f)
}
var terr error
t, terr = t.ParseFiles(files...)
if terr != nil {
log.Fatalf("Failed to parse templates: %v", terr)
}
return t
}
func main() {
// 加载配置文件(自动生成 + 补全缺失项)
if err := config.Load(); err != nil {
log.Fatalf("加载配置失败: %v", err)
}
log.Printf("配置加载成功,数据目录: %s,数据库: %s", config.Cfg.Data.Dir, config.Cfg.Database.Type)
// 初始化数据库
if err := database.InitDB(); err != nil {
log.Fatalf("初始化数据库失败: %v", err)
}
defer database.CloseDB()
// 创建上传目录
if err := os.MkdirAll(config.GetUploadDir(), 0755); err != nil {
log.Fatalf("创建上传目录失败: %v", err)
}
// 创建 session 存储
sessionStore := session.NewSessionStore()
// 创建 IP 封禁守护
ipBanGuard := middleware.NewIPBanGuard()
// 设置 Gin 模式
ginMode := os.Getenv("GIN_MODE")
if ginMode == "" {
gin.SetMode(gin.DebugMode)
}
r := gin.Default()
// 加载 HTML 模板
r.SetHTMLTemplate(loadTemplates())
// 静态文件
r.Static("/static", "./static")
// 注入 session 和 IP 封禁守护
r.Use(func(c *gin.Context) {
c.Set("sessionStore", sessionStore)
c.Set("ipBanGuard", ipBanGuard)
c.Next()
})
// 公开路由
r.GET("/", handlers.HomeHandler)
r.GET("/click/:id", handlers.CardClickHandler)
r.GET("/search", handlers.SearchHandler)
r.GET("/uploads/:filename", handlers.ServeUploadHandler)
// 后台路由(IP 白名单)
adminGroup := r.Group("/admin")
adminGroup.Use(middleware.IPWhitelistRequired(func(sessionID string) bool {
return sessionStore.Get(sessionID) != nil
}))
{
// 登录(无需认证,受 IP 白名单限制)
adminGroup.GET("/login", handlers.LoginGet)
adminGroup.POST("/login", handlers.LoginPost)
// 需要认证的后台路由
protected := adminGroup.Group("")
protected.Use(middleware.AuthRequired(sessionStore))
{
protected.POST("/logout", handlers.Logout)
protected.GET("/", handlers.AdminIndex)
// 卡片管理
protected.GET("/cards", handlers.CardsList)
protected.GET("/cards/new", handlers.CardCreateGet)
protected.POST("/cards", handlers.CardCreatePost)
protected.GET("/cards/:id/edit", handlers.CardEditGet)
protected.POST("/cards/:id", handlers.CardEditPost)
protected.POST("/cards/:id/delete", handlers.CardDelete)
protected.POST("/cards/:id/toggle", handlers.CardToggle)
protected.POST("/cards/:id/move-up", handlers.CardMoveUp)
protected.POST("/cards/:id/move-down", handlers.CardMoveDown)
// 图片上传
protected.POST("/upload", handlers.UploadHandler)
// 设置
protected.GET("/settings", handlers.SettingsGet)
protected.POST("/settings", handlers.SettingsPost)
// 安全:登录日志
protected.GET("/logs", handlers.LoginLogsGet)
protected.POST("/logs/unban/:id", handlers.UnbanIP)
// 安全:修改密码
protected.GET("/password", handlers.ChangePasswordGet)
protected.POST("/password", handlers.ChangePasswordPost)
// 安全:IP 白名单
protected.GET("/ip-whitelist", handlers.IPWhitelistGet)
protected.POST("/ip-whitelist/add", handlers.IPWhitelistAdd)
protected.POST("/ip-whitelist/:id/delete", handlers.IPWhitelistDelete)
// 分析:访问日志
protected.GET("/access-logs", handlers.AccessLogsGet)
}
}
// 启动服务器:同时监听 TCP 和 Unix socket(如果配置了的话)
var wg sync.WaitGroup
if config.Cfg.Server.Unix != "" {
wg.Add(1)
go func() {
defer wg.Done()
// 清理残留的 socket 文件
os.Remove(config.Cfg.Server.Unix)
listener, err := net.Listen("unix", config.Cfg.Server.Unix)
if err != nil {
log.Fatalf("监听 Unix socket 失败: %v", err)
}
// 设置 socket 文件权限,允许 nginx 等其他进程访问
os.Chmod(config.Cfg.Server.Unix, 0666)
log.Printf("监听 Unix socket: %s", config.Cfg.Server.Unix)
if err := r.RunListener(listener); err != nil {
log.Fatalf("Unix socket 服务启动失败: %v", err)
}
}()
}
// TCP 始终监听
wg.Add(1)
go func() {
defer wg.Done()
addr := config.Cfg.Server.Addr
log.Printf("监听 TCP: %s", addr)
if err := r.Run(addr); err != nil {
log.Fatalf("TCP 服务启动失败: %v", err)
}
}()
wg.Wait()
}