package main import ( "embed" "fmt" "io" "io/fs" "net/http" "os" "strings" "time" "web-giao-ban-go/config" "web-giao-ban-go/handlers" "web-giao-ban-go/logger" "web-giao-ban-go/tray" ) //go:embed public/* var embeddedFiles embed.FS func main() { // Initialize logger logger.Init("logs/app.log", 500) logger.Info("=======================================================") logger.Info("[DH-HIS] Phần Mềm Quản Lý Giao Ban Bệnh Viện (Golang)") logger.Info("=======================================================") // Load or create config cfg, err := config.LoadConfig("config.json") if err != nil { logger.Error("Không thể đọc file config.json: %v", err) } else { logger.Info("Đã nạp file cấu hình thành công (Port: %d, Admin: %s)", cfg.Server.Port, cfg.Server.AdminPath) } handlers.EnsureDirs() // Run system tray (Windows GUI) or signal listener (Linux daemon) tray.Run(func() { startWebServer() }) } func startWebServer() { cfg := config.Get() port := cfg.Server.Port if port <= 0 { port = 3000 } mux := http.NewServeMux() // Sub-filesystem for embedded public pubFS, err := fs.Sub(embeddedFiles, "public") if err != nil { logger.Error("Lỗi đọc embedded filesystem: %v", err) return } // Static file handler staticHandler := func(w http.ResponseWriter, r *http.Request) { p := r.URL.Path p = strings.TrimPrefix(p, "/web-giao-ban-go") p = strings.TrimPrefix(p, "/web-giao-ban") if p == "" { p = "/" } cleanPath := strings.TrimPrefix(p, "/") if cleanPath == "" { cleanPath = "index.html" } // Disable caching for HTML to avoid browser redirect caching if strings.HasSuffix(cleanPath, ".html") || cleanPath == "index.html" { w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") w.Header().Set("Pragma", "no-cache") w.Header().Set("Expires", "0") } // Try open in embedded fs if f, err := pubFS.Open(cleanPath); err == nil { defer f.Close() if stat, err := f.Stat(); err == nil && !stat.IsDir() { http.ServeContent(w, r, stat.Name(), stat.ModTime(), f.(io.ReadSeeker)) return } } // Fallback to index.html for SPA routing if f, err := pubFS.Open("index.html"); err == nil { defer f.Close() if stat, err := f.Stat(); err == nil { w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") http.ServeContent(w, r, "index.html", stat.ModTime(), f.(io.ReadSeeker)) return } } http.NotFound(w, r) } // Serve Uploaded Files _ = os.MkdirAll("uploads/issues", 0755) uploadFileServer := http.StripPrefix("/uploads/", http.FileServer(http.Dir("uploads"))) uploadFileServerPrefixed := http.StripPrefix("/web-giao-ban/uploads/", http.FileServer(http.Dir("uploads"))) uploadFileServerPrefixedGo := http.StripPrefix("/web-giao-ban-go/uploads/", http.FileServer(http.Dir("uploads"))) mux.Handle("/uploads/", uploadFileServer) mux.Handle("/web-giao-ban/uploads/", uploadFileServerPrefixed) mux.Handle("/web-giao-ban-go/uploads/", uploadFileServerPrefixedGo) // Admin page adminPath := cfg.Server.AdminPath if adminPath == "" { adminPath = "/admin" } adminHandler := func(w http.ResponseWriter, r *http.Request) { if f, err := pubFS.Open("admin.html"); err == nil { defer f.Close() if stat, err := f.Stat(); err == nil { http.ServeContent(w, r, "admin.html", stat.ModTime(), f.(io.ReadSeeker)) return } } http.NotFound(w, r) } mux.HandleFunc(adminPath, adminHandler) mux.HandleFunc("/web-giao-ban"+adminPath, adminHandler) mux.HandleFunc("/web-giao-ban-go"+adminPath, adminHandler) // API Routing helper to mount on /api/..., /web-giao-ban/api/..., and /web-giao-ban-go/api/... mountAPI := func(pattern string, handler func(http.ResponseWriter, *http.Request)) { mux.HandleFunc(pattern, handler) mux.HandleFunc("/web-giao-ban"+pattern, handler) mux.HandleFunc("/web-giao-ban-go"+pattern, handler) } // Business APIs mountAPI("/api/databases", handlers.HandleGetDatabases) mountAPI("/api/giao-ban-ngoai-tru", handlers.HandleGiaoBanNgoaiTru) mountAPI("/api/giao-ban-noi-tru", handlers.HandleGiaoBanNoiTru) mountAPI("/api/danh-sach-chuyen-vien", handlers.HandleDanhSachChuyenVien) mountAPI("/api/danh-sach-theo-doi", handlers.HandleDanhSachTheoDoi) mountAPI("/api/cap-nhat-ghi-chu", handlers.HandleCapNhatGhiChu) // Issues Management APIs mountAPI("/api/issues", func(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: handlers.HandleGetIssues(w, r) case http.MethodPost: handlers.HandleCreateIssue(w, r) default: http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) } }) mountAPI("/api/issues/", func(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodPut: handlers.HandleUpdateIssue(w, r) case http.MethodDelete: handlers.HandleDeleteIssue(w, r) default: http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) } }) // Admin & Update APIs mountAPI("/api/admin/config", func(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: handlers.HandleGetAdminConfig(w, r) case http.MethodPost: handlers.HandleSaveAdminConfig(w, r) default: http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) } }) mountAPI("/api/admin/test-db", handlers.HandleTestDBConnection) mountAPI("/api/admin/logs", handlers.HandleGetLogs) mountAPI("/api/admin/logs/clear", handlers.HandleClearLogs) mountAPI("/api/update/check", handlers.HandleCheckUpdate) mountAPI("/api/update/apply", handlers.HandleApplyUpdate) // Static website routes mux.HandleFunc("/web-giao-ban-go", staticHandler) mux.HandleFunc("/web-giao-ban-go/", staticHandler) mux.HandleFunc("/web-giao-ban", staticHandler) mux.HandleFunc("/web-giao-ban/", staticHandler) mux.HandleFunc("/", staticHandler) // Enable CORS & Logging Middleware corsHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With") if r.Method == http.MethodOptions { w.WriteHeader(http.StatusOK) return } t0 := time.Now() mux.ServeHTTP(w, r) if strings.HasPrefix(r.URL.Path, "/api/") || strings.HasPrefix(r.URL.Path, "/web-giao-ban/api/") || strings.HasPrefix(r.URL.Path, "/web-giao-ban-go/api/") { logger.Info("%s %s in %v", r.Method, r.URL.Path, time.Since(t0)) } }) addr := fmt.Sprintf("0.0.0.0:%d", port) logger.Info("Web Server đang lắng nghe tại: http://localhost:%d", port) logger.Info("Trang Giao Ban: http://localhost:%d/web-giao-ban-go (hoặc /web-giao-ban)", port) logger.Info("Trang Quản Trị: http://localhost:%d/web-giao-ban-go%s", port, adminPath) server := &http.Server{ Addr: addr, Handler: corsHandler, ReadTimeout: 30 * time.Second, WriteTimeout: 60 * time.Second, } if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { logger.Error("Lỗi khởi động web server: %v", err) } }