package app import ( "context" "net/http" "os" "path/filepath" "strings" "time" "git.118im.com/im_team/im_workbench/internal/accounts" "git.118im.com/im_team/im_workbench/internal/auth" "git.118im.com/im_team/im_workbench/internal/config" "git.118im.com/im_team/im_workbench/internal/finance" "git.118im.com/im_team/im_workbench/internal/httpapi" "git.118im.com/im_team/im_workbench/internal/mall" "git.118im.com/im_team/im_workbench/internal/portal" "git.118im.com/im_team/im_workbench/internal/store" "git.118im.com/im_team/im_workbench/internal/tasks" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/redis/go-redis/v9" ) type App struct { store *store.Store redis *redis.Client api *httpapi.API frontendPath string } func New(cfg *config.Config) (*App, error) { st, err := store.Open(cfg.MySQL) if err != nil { return nil, err } redisClient := redis.NewClient(&redis.Options{ Addr: cfg.Redis.Addr, Username: cfg.Redis.Username, Password: cfg.Redis.Password, DB: cfg.Redis.DB, }) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := redisClient.Ping(ctx).Err(); err != nil { _ = redisClient.Close() _ = st.Close() return nil, err } authenticator := auth.New(cfg.Auth, redisClient) accountService := accounts.New(st, redisClient, cfg.Auth) portalService := portal.New(st) taskService := tasks.New(st) mallService := mall.New(st) financeService := finance.New(st, redisClient, cfg.Auth) return &App{store: st, redis: redisClient, api: httpapi.New(authenticator, accountService, portalService, taskService, mallService, financeService), frontendPath: cfg.FrontendDir}, nil } func (a *App) Handler() http.Handler { mux := http.NewServeMux() if a.frontendDir() != "" { mux.Handle("/admin", frontendHandler(a.frontendDir())) mux.Handle("/admin/", frontendHandler(a.frontendDir())) } mux.HandleFunc("/healthz", a.api.Healthz) mux.Handle("/metrics", promhttp.Handler()) a.api.Register(mux) return mux } func (a *App) frontendDir() string { if dir := strings.TrimSpace(os.Getenv("WORKBENCH_FRONTEND_DIR")); dir != "" { return dir } return strings.TrimSpace(a.frontendPath) } func frontendHandler(dir string) http.Handler { files := http.FileServer(http.Dir(dir)) index := filepath.Join(dir, "index.html") return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { path := strings.TrimPrefix(r.URL.Path, "/admin") if path == "" { path = "/" } candidate := filepath.Join(dir, filepath.Clean(path)) if info, err := os.Stat(candidate); err == nil && !info.IsDir() { r2 := r.Clone(r.Context()) r2.URL.Path = path files.ServeHTTP(w, r2) return } http.ServeFile(w, r, index) }) } func (a *App) Close() { if a.redis != nil { _ = a.redis.Close() } if a.store != nil { _ = a.store.Close() } }