Files
Prism/internal/service/app.go
T
renjue 59d1087cc9
CI / test (push) Failing after 1m46s
CI / docker (push) Has been skipped
feat: Prism HTTP/SOCKS5 代理网关及管理台
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-23 15:08:50 +08:00

151 lines
3.6 KiB
Go

package service
import (
"context"
"fmt"
"sync"
"time"
"github.com/prism/proxy/internal/cache"
"github.com/prism/proxy/internal/configbuilder"
"github.com/prism/proxy/internal/core"
"github.com/prism/proxy/internal/geodata"
applog "github.com/prism/proxy/internal/log"
"github.com/prism/proxy/internal/mihomoapi"
"github.com/prism/proxy/internal/store"
"github.com/prism/proxy/internal/subscription"
)
type App struct {
Store *store.Store
Cache *cache.Cache
Engine *core.Engine
Builder *configbuilder.Builder
Subscription *subscription.Service
Logs *applog.Collector
DataDir string
LastReloadError string
engineMu sync.RWMutex
engineOK bool
engineAt time.Time
}
func (a *App) ReloadEngine(ctx context.Context) error {
geodata.InitHomeDir(a.DataDir)
settings, err := a.Store.GetSettings(ctx)
if err != nil {
return err
}
geodata.ApplyMihomoURLs(geodata.ConfigFromSettings(settings))
yaml, err := a.Builder.Build(ctx)
if err != nil {
return err
}
apiPort := settings["mihomo_api_port"]
if apiPort == "" {
apiPort = "9090"
}
secret := settings["api_secret"]
a.Engine.Configure("127.0.0.1:"+apiPort, secret)
if err := a.applyConfig(ctx, yaml); err != nil {
a.Logs.Add("warn", "prism", "full config failed, trying minimal: "+err.Error())
minimal, mErr := a.Builder.BuildMinimal(ctx)
if mErr != nil {
return err
}
if mErr := a.applyConfig(ctx, minimal); mErr != nil {
a.LastReloadError = err.Error()
if last := a.Cache.GetLastYAML(); last != nil {
if fallbackErr := a.Engine.Reload(last); fallbackErr == nil {
a.Logs.Add("warn", "prism", "kept previous engine config after reload failure")
}
}
return err
}
a.LastReloadError = "rules simplified: " + err.Error()
a.Logs.Add("warn", "prism", a.LastReloadError)
return nil
}
return nil
}
func (a *App) applyConfig(ctx context.Context, yaml []byte) error {
if err := a.Engine.Reload(yaml); err != nil {
return err
}
a.LastReloadError = ""
a.Cache.SetLastYAML(yaml)
a.Logs.Add("info", "prism", "engine reloaded successfully")
a.engineMu.Lock()
a.engineOK = true
a.engineAt = time.Now()
a.engineMu.Unlock()
_ = a.Store.AddAuditLog(ctx, "reload", "engine", "config applied")
return nil
}
func (a *App) MihomoAPIBase(ctx context.Context) (string, string, error) {
settings, err := a.Store.GetSettings(ctx)
if err != nil {
return "", "", err
}
port := settings["mihomo_api_port"]
if port == "" {
port = "9090"
}
return fmt.Sprintf("http://127.0.0.1:%s", port), settings["api_secret"], nil
}
func (a *App) EngineReachable(ctx context.Context) bool {
if a.Engine.Running() {
return true
}
a.engineMu.RLock()
if time.Since(a.engineAt) < 15*time.Second {
ok := a.engineOK
a.engineMu.RUnlock()
return ok
}
a.engineMu.RUnlock()
ok := a.probeEngine(ctx)
a.engineMu.Lock()
a.engineOK = ok
a.engineAt = time.Now()
a.engineMu.Unlock()
return ok
}
// EngineStatusCached returns last-known engine reachability without blocking on Mihomo.
func (a *App) EngineStatusCached() bool {
if a.Engine.Running() {
return true
}
a.engineMu.RLock()
defer a.engineMu.RUnlock()
return a.engineOK
}
func (a *App) RefreshEngineStatusAsync() {
go func() {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
ok := a.probeEngine(ctx)
a.engineMu.Lock()
a.engineOK = ok
a.engineAt = time.Now()
a.engineMu.Unlock()
}()
}
func (a *App) probeEngine(ctx context.Context) bool {
base, secret, err := a.MihomoAPIBase(ctx)
if err != nil {
return false
}
return mihomoapi.New(base, secret).Reachable()
}