c26b06f1cc
Go 控制面(SQLite + REST API)嵌入 Mihomo 数据面,Vue 3 多语言 Web 管理台。 含订阅/节点/规则/出站/监控/日志、OpenAPI、API Key、Gitea Actions CI 与开源文档; CI 使用 ubuntu-latest runner,Go/Docker 步骤走国内镜像与 shell,避免 GitHub 下载超时。 Co-authored-by: Cursor <cursoragent@cursor.com>
151 lines
3.6 KiB
Go
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()
|
|
}
|