feat: DH-HIS Web Giao Ban Go v1.0.1 (Favicon, Exe icon, System Tray, Gitea auto-update)
This commit is contained in:
@@ -0,0 +1,228 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user