Files
web-giao-ban-go/main.go
T

237 lines
7.3 KiB
Go

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/license"
"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)
}
// Initialize License
license.InitLicense()
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)
// License APIs
mountAPI("/api/license/status", handlers.HandleGetLicenseStatus)
mountAPI("/api/license/activate", handlers.HandleActivateLicense)
// 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)
}
}