237 lines
7.3 KiB
Go
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)
|
|
}
|
|
}
|