// ClearLove 表白墙 2.0 —— 单二进制部署的表白墙 Web 应用 // 入口:加载配置 -> 连接数据库 -> 解析模板 -> 注册路由 -> 启动 HTTP 服务与 AI 巡查 package main import ( "embed" "fmt" "io/fs" "log" "net/http" "os" "path/filepath" "clearlove/internal/ai" "clearlove/internal/config" "clearlove/internal/database" "clearlove/internal/handlers" "clearlove/internal/middleware" ) // webFS 嵌入全部模板与静态资源,保证单文件部署 // //go:embed all:web/templates all:web/static var webFS embed.FS func main() { config.Load() // 运行时目录(单二进制部署时自动创建,无需手工准备) if err := ensureDirs(); err != nil { log.Fatalf("目录初始化失败: %v\n提示:可通过环境变量 CLEARLOVE_DATA_DIR / CLEARLOVE_UPLOAD_DIR 指定可写目录", err) } if config.Cfg.Installed { if err := database.Connect(); err != nil { log.Fatalf("数据库连接失败: %v", err) } if err := database.Migrate(); err != nil { log.Fatalf("数据库迁移失败: %v", err) } } handlers.SetupTemplates(webFS) pluginFS, _ := fs.Sub(webFS, "web/static") handlers.StaticFS = pluginFS go ai.StartPatrol() // AI 自动巡查(每10分钟,仅在开启时工作) // 监听端口:PORT 或 CLEARLOVE_PORT,默认 16868 addr := ":16868" if p := os.Getenv("PORT"); p != "" { addr = ":" + p } else if p := os.Getenv("CLEARLOVE_PORT"); p != "" { addr = ":" + p } log.Printf("ClearLove 表白墙 2.0 | 数据目录: %s | 上传目录: %s", config.Cfg.DataDir, config.Cfg.UploadDir) if config.Cfg.Installed { log.Printf("服务已启动: http://localhost%s (后台: /admin)", addr) } else { log.Printf("检测到未安装,请访问 http://localhost%s/install 完成安装向导", addr) } if err := http.ListenAndServe(addr, middleware.Use(buildMux())); err != nil { log.Fatalf("HTTP 服务启动失败: %v", err) } } // ensureDirs 创建并校验运行时目录可写性(提前失败,避免运行中出错) func ensureDirs() error { dirs := []string{ config.Cfg.DataDir, config.Cfg.UploadDir, filepath.Join(config.Cfg.DataDir, "themes"), filepath.Join(config.Cfg.DataDir, "plugins"), } for _, d := range dirs { if err := os.MkdirAll(d, 0o755); err != nil { return err } probe := filepath.Join(d, ".write_test") if err := os.WriteFile(probe, []byte("ok"), 0o644); err != nil { return fmt.Errorf("%s 不可写: %w", d, err) } _ = os.Remove(probe) } return nil } // buildMux 注册全部路由(Go 1.22+ 增强路由语法:方法 + 路径参数) func buildMux() *http.ServeMux { mux := http.NewServeMux() // 静态资源与上传文件 mux.Handle("GET /static/", http.StripPrefix("/static/", http.FileServer(http.FS(handlers.StaticFS)))) mux.HandleFunc("GET /favicon.svg", handlers.Favicon) mux.HandleFunc("GET /theme.css", handlers.ThemeCSS) mux.Handle("GET /uploads/", http.StripPrefix("/uploads/", http.FileServer(http.Dir(config.Cfg.UploadDir)))) // 安装向导 mux.HandleFunc("GET /install", handlers.InstallPage) mux.HandleFunc("POST /install", handlers.InstallSubmit) // 前台 mux.HandleFunc("GET /{$}", handlers.Index) mux.HandleFunc("GET /post/{id}", handlers.PostDetail) mux.HandleFunc("GET /post/{id}/edit", handlers.PostEdit) mux.HandleFunc("POST /post/{id}/edit", handlers.PostEditSave) mux.HandleFunc("POST /post/{id}/delete", handlers.PostDelete) mux.HandleFunc("GET /compose", handlers.ComposePage) mux.HandleFunc("POST /compose", handlers.ComposeSubmit) mux.HandleFunc("GET /login", handlers.LoginPage) mux.HandleFunc("POST /login", handlers.LoginSubmit) mux.HandleFunc("GET /register", handlers.RegisterPage) mux.HandleFunc("POST /register", handlers.RegisterSubmit) mux.HandleFunc("POST /api/register-code", handlers.RegisterCode) mux.HandleFunc("GET /logout", handlers.Logout) mux.HandleFunc("GET /u/{id}", handlers.Profile) // 通用 API(会话或 API Key 鉴权) mux.HandleFunc("GET /api/captcha", handlers.CaptchaJSON) mux.HandleFunc("GET /api/v1/posts", handlers.APIListPosts) mux.HandleFunc("GET /api/v1/posts/{id}", handlers.APIGetPost) mux.HandleFunc("POST /api/v1/posts", handlers.APICreatePost) mux.HandleFunc("POST /api/v1/upload", handlers.APIUpload) mux.HandleFunc("POST /api/v1/posts/{id}/like", handlers.APILike) mux.HandleFunc("POST /api/v1/comments", handlers.APIComment) mux.HandleFunc("GET /api/v1/topics", handlers.APITopics) mux.HandleFunc("POST /api/v1/reports", handlers.APIReport) // 后台管理 mux.HandleFunc("GET /admin/login", handlers.AdminLoginPage) mux.HandleFunc("POST /admin/login", handlers.AdminLoginSubmit) mux.HandleFunc("GET /admin/logout", handlers.AdminLogout) mux.HandleFunc("GET /admin", handlers.AdminDashboard) mux.HandleFunc("GET /admin/posts", handlers.AdminPosts) mux.HandleFunc("POST /admin/posts/delete", handlers.AdminPostDelete) mux.HandleFunc("POST /admin/posts/ban", handlers.AdminPostBan) mux.HandleFunc("GET /admin/settings", handlers.AdminSettings) mux.HandleFunc("POST /admin/settings", handlers.AdminSettingsSave) mux.HandleFunc("POST /admin/theme/upload", handlers.AdminThemeUpload) mux.HandleFunc("POST /admin/theme/switch", handlers.AdminThemeSwitch) mux.HandleFunc("POST /admin/theme/delete", handlers.AdminThemeDelete) mux.HandleFunc("GET /admin/reports", handlers.AdminReports) mux.HandleFunc("POST /admin/reports/handle", handlers.AdminReportHandle) mux.HandleFunc("POST /admin/reports/patrol", handlers.AdminPatrolStart) mux.HandleFunc("GET /admin/reports/patrol/status", handlers.AdminPatrolStatus) mux.HandleFunc("GET /admin/users", handlers.AdminUsers) mux.HandleFunc("POST /admin/users/status", handlers.AdminUserStatus) mux.HandleFunc("POST /admin/users/delete", handlers.AdminUserDelete) mux.HandleFunc("GET /admin/admins", handlers.AdminAdmins) mux.HandleFunc("POST /admin/admins/create", handlers.AdminCreate) mux.HandleFunc("POST /admin/admins/delete", handlers.AdminDelete) mux.HandleFunc("GET /admin/notices", handlers.AdminNotices) mux.HandleFunc("POST /admin/notices", handlers.AdminNoticesSave) mux.HandleFunc("GET /admin/plugins", handlers.AdminPlugins) mux.HandleFunc("POST /admin/plugins/upload", handlers.AdminPluginUpload) mux.HandleFunc("POST /admin/plugins/toggle", handlers.AdminPluginToggle) mux.HandleFunc("POST /admin/plugins/delete", handlers.AdminPluginDelete) // 插件商店(对接云端插件社区) mux.HandleFunc("GET /admin/store", handlers.AdminStore) mux.HandleFunc("POST /admin/store/install", handlers.AdminStoreInstall) mux.HandleFunc("GET /admin/apikeys", handlers.AdminAPIKeys) mux.HandleFunc("POST /admin/apikeys/create", handlers.AdminAPIKeyCreate) mux.HandleFunc("POST /admin/apikeys/delete", handlers.AdminAPIKeyDelete) mux.HandleFunc("GET /admin/about", handlers.AdminAbout) mux.HandleFunc("GET /admin/update", handlers.AdminUpdate) return mux }