Files
Prism/internal/service/outbound.go
T
rose_cat707 fc3a66dc83
CI / docker (push) Has been cancelled
CI / test (push) Has been cancelled
feat: Prism HTTP/SOCKS5 代理网关及管理台
Go 控制面(SQLite + REST API)嵌入 Mihomo 数据面,Vue 3 多语言 Web 管理台。
含订阅/节点/规则/出站/监控/日志、OpenAPI、API Key、Gitea Actions CI(runs-on: ubuntu-latest)
与开源文档;镜像发布至 git.rc707blog.top Container Registry。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-23 13:41:56 +08:00

167 lines
4.5 KiB
Go

package service
import (
"context"
"fmt"
"strings"
"time"
"github.com/prism/proxy/internal/country"
"github.com/prism/proxy/internal/mihomoapi"
"github.com/prism/proxy/internal/store"
)
type OutboundMember struct {
RuntimeName string `json:"runtime_name"`
Name string `json:"name"`
}
type OutboundGroup struct {
RuntimeName string `json:"runtime_name"`
Name string `json:"name"`
Type string `json:"type"`
Source string `json:"source"`
Now string `json:"now"`
NowName string `json:"now_name"`
Selectable bool `json:"selectable"`
IsCountry bool `json:"is_country"`
Members []OutboundMember `json:"members"`
}
type OutboundOverview struct {
DefaultPolicy string `json:"default_policy"`
Groups []OutboundGroup `json:"groups"`
}
func (a *App) OutboundOverview(ctx context.Context) (OutboundOverview, error) {
settings, err := a.Store.GetSettings(ctx)
if err != nil {
return OutboundOverview{}, err
}
defaultPolicy := settings["default_policy"]
if defaultPolicy == "" {
defaultPolicy = "DIRECT"
}
base, secret, err := a.MihomoAPIBase(ctx)
if err != nil {
return OutboundOverview{}, err
}
live, err := mihomoapi.New(base, secret).ListProxiesCached(8 * time.Second)
if err != nil {
return OutboundOverview{}, fmt.Errorf("mihomo proxies: %w", err)
}
nodes, err := a.Store.ListProxyNodes(ctx, "", nil)
if err != nil {
return OutboundOverview{}, err
}
groups, err := a.Store.ListProxyGroups(ctx)
if err != nil {
return OutboundOverview{}, err
}
display := buildDisplayMap(nodes, groups)
var outGroups []OutboundGroup
seen := map[string]bool{}
for _, g := range groups {
if !g.Enabled {
continue
}
state, ok := live[g.RuntimeName]
if !ok || !mihomoapi.IsPolicyGroupType(state.Type) {
continue
}
seen[g.RuntimeName] = true
outGroups = append(outGroups, buildOutboundGroup(g.RuntimeName, g.Name, g.Source, state, display))
}
for name, state := range live {
if seen[name] || !mihomoapi.IsPolicyGroupType(state.Type) {
continue
}
outGroups = append(outGroups, buildOutboundGroup(name, display[name], "runtime", state, display))
}
return OutboundOverview{
DefaultPolicy: defaultPolicy,
Groups: outGroups,
}, nil
}
func (a *App) SelectOutboundGroup(ctx context.Context, groupRuntime, proxyRuntime string) error {
base, secret, err := a.MihomoAPIBase(ctx)
if err != nil {
return err
}
if err := mihomoapi.New(base, secret).SelectProxy(groupRuntime, proxyRuntime); err != nil {
return err
}
_ = a.Store.AddAuditLog(ctx, "select", "outbound", groupRuntime+" -> "+proxyRuntime)
return nil
}
func (a *App) SetDefaultPolicy(ctx context.Context, policy string) error {
if err := a.Store.SetSettings(ctx, map[string]string{"default_policy": policy}); err != nil {
return err
}
return a.ReloadEngine(ctx)
}
func buildOutboundGroup(runtimeName, name, source string, state mihomoapi.ProxyState, display map[string]string) OutboundGroup {
if name == "" {
name = runtimeName
}
autoOnly := state.Type == "URLTest" || state.Type == "Fallback"
members := make([]OutboundMember, 0, len(state.All))
for _, m := range state.All {
if autoOnly && (m == "DIRECT" || m == "REJECT" || m == "REJECT-DROP") {
continue
}
if m == "DIRECT" || m == "REJECT" || m == "REJECT-DROP" {
members = append(members, OutboundMember{RuntimeName: m, Name: m})
continue
}
members = append(members, OutboundMember{
RuntimeName: m,
Name: displayName(display, m),
})
}
return OutboundGroup{
RuntimeName: runtimeName,
Name: name,
Type: state.Type,
Source: source,
Now: state.Now,
NowName: displayName(display, state.Now),
Selectable: state.Type == "Selector",
IsCountry: strings.HasPrefix(runtimeName, "country_"),
Members: members,
}
}
func buildDisplayMap(nodes []store.ProxyNode, groups []store.ProxyGroup) map[string]string {
m := map[string]string{
"DIRECT": "DIRECT",
"REJECT": "REJECT",
}
seen := map[string]bool{}
for _, n := range nodes {
m[n.RuntimeName] = n.Name
if n.Country != "" && !seen[n.Country] {
seen[n.Country] = true
m[country.GroupRuntime(n.Country)] = country.Name(n.Country) + "(国家池)"
}
}
for _, g := range groups {
m[g.RuntimeName] = g.Name
}
return m
}
func displayName(display map[string]string, runtime string) string {
if name, ok := display[runtime]; ok && name != "" {
return name
}
return runtime
}