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 按配置监听 addr := config.Cfg.Server.Addr unix := config.Cfg.Server.Unix if addr == "" && unix == "" { log.Fatal("未配置任何监听地址,请设置 server.addr 或 server.unix") } var wg sync.WaitGroup started := 0 if unix != "" { wg.Add(1) started++ go func() { defer wg.Done() // 清理残留的 socket 文件 os.Remove(unix) listener, err := net.Listen("unix", unix) if err != nil { log.Fatalf("监听 Unix socket 失败: %v", err) } // 设置 socket 文件权限,允许 nginx 等其他进程访问 os.Chmod(unix, 0666) log.Printf("监听 Unix socket: %s", unix) if err := r.RunListener(listener); err != nil { log.Fatalf("Unix socket 服务启动失败: %v", err) } }() } if addr != "" && addr != ":0" { wg.Add(1) started++ go func() { defer wg.Done() log.Printf("监听 TCP: %s", addr) if err := r.Run(addr); err != nil { log.Fatalf("TCP 服务启动失败: %v", err) } }() } if started == 0 { log.Fatal("未配置任何有效监听地址") } wg.Wait() }