mirror of
https://github.com/e1732a364fed/v2ray_simple.git
synced 2025-12-24 13:27:56 +08:00
修订cmd,gui;添加gui路由配置页面
This commit is contained in:
@@ -41,7 +41,7 @@ func flist(list []*CliCmd) (result []func()) {
|
||||
var cliCmdList = []*CliCmd{
|
||||
{
|
||||
"查询当前状态", func() {
|
||||
mainM.PrintAllState(os.Stdout)
|
||||
mainM.PrintAllState(os.Stdout, false)
|
||||
},
|
||||
}, {
|
||||
"打印当前版本所支持的所有协议", printSupportedProtocols,
|
||||
@@ -290,7 +290,7 @@ func interactively_hotRemoveServerOrClient(m *machine.M) {
|
||||
utils.PrintStr("即将开始热删除配置步骤, 删除正在运行的配置可能有未知风险,谨慎操作\n")
|
||||
utils.PrintStr("【当前所有配置】为:\n")
|
||||
utils.PrintStr(delimiter)
|
||||
m.PrintAllState(os.Stdout)
|
||||
m.PrintAllState(os.Stdout, false)
|
||||
|
||||
var items []string
|
||||
if m.ServerCount() > 0 {
|
||||
@@ -388,7 +388,7 @@ func interactively_hotRemoveServerOrClient(m *machine.M) {
|
||||
|
||||
utils.PrintStr("删除成功!当前状态:\n")
|
||||
utils.PrintStr(delimiter)
|
||||
m.PrintAllState(os.Stdout)
|
||||
m.PrintAllState(os.Stdout, false)
|
||||
}
|
||||
|
||||
func interactively_hotLoadUrlConfig(m *machine.M) {
|
||||
@@ -459,7 +459,7 @@ func interactively_hotLoadConfigFile(m *machine.M) {
|
||||
utils.PrintStr("【注意】我们交互模式只支持热添加listen和dial, 对于dns/route/fallback的热增删, 请期待api server未来的实现.\n")
|
||||
utils.PrintStr("【当前所有配置】为:\n")
|
||||
utils.PrintStr(delimiter)
|
||||
m.PrintAllState(os.Stdout)
|
||||
m.PrintAllState(os.Stdout, false)
|
||||
|
||||
utils.PrintStr("请输入你想添加的文件名称\n")
|
||||
|
||||
@@ -507,7 +507,7 @@ func interactively_hotLoadConfigFile(m *machine.M) {
|
||||
|
||||
utils.PrintStr("添加成功!当前状态:\n")
|
||||
utils.PrintStr(delimiter)
|
||||
m.PrintAllState(os.Stdout)
|
||||
m.PrintAllState(os.Stdout, false)
|
||||
}
|
||||
|
||||
func interactively_adjust_loglevel() {
|
||||
|
||||
@@ -151,7 +151,7 @@ func makeBasicControlsPage() ui.Control {
|
||||
|
||||
printStateBtn := ui.NewButton("打印当前状态")
|
||||
printStateBtn.OnClicked(func(b *ui.Button) {
|
||||
mainM.PrintAllStateForHuman(os.Stdout)
|
||||
mainM.PrintAllStateForHuman(os.Stdout, false)
|
||||
})
|
||||
|
||||
toggleHbox.Append(printStateBtn, false)
|
||||
@@ -502,6 +502,7 @@ func setupTab() {
|
||||
|
||||
tab.Append("基础控制", makeBasicControlsPage())
|
||||
tab.Append("代理控制", makeConfPage())
|
||||
tab.Append("路由控制", makeRoutePage())
|
||||
tab.Append("app控制", makeAppPage())
|
||||
|
||||
//for i := 0; i < tab.NumPages(); i++ {
|
||||
|
||||
@@ -673,7 +673,7 @@ func addConfControls(sc proxy.StandardConf, vb *ui.Box, isDial bool) {
|
||||
mainM.Start()
|
||||
}
|
||||
|
||||
mainM.PrintAllStateForHuman(os.Stdout)
|
||||
mainM.PrintAllStateForHuman(os.Stdout, false)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
198
cmd/verysimple/gui_route.go
Normal file
198
cmd/verysimple/gui_route.go
Normal file
@@ -0,0 +1,198 @@
|
||||
//go:build gui
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/e1732a364fed/ui"
|
||||
"github.com/e1732a364fed/v2ray_simple/netLayer"
|
||||
"github.com/e1732a364fed/v2ray_simple/utils"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func makeRoutePage() ui.Control {
|
||||
|
||||
vb := ui.NewVerticalBox()
|
||||
vb.SetPadded(true)
|
||||
|
||||
vb.Append(ui.NewLabel("目前vs_gui只支持路由单项配置,不支持数组; 若要使用数组, 请手动编辑配置文件"), false)
|
||||
vb.Append(ui.NewLabel("编辑字符串时, 请确保按回车确认. 之后还要点击提交更改. 否则更改不会生效"), false)
|
||||
|
||||
newBtn := ui.NewButton("新增")
|
||||
rmBtn := ui.NewButton("删除")
|
||||
submitBtn := ui.NewButton("提交更改")
|
||||
detailBtn := ui.NewButton("详细说明")
|
||||
|
||||
hb := ui.NewHorizontalBox()
|
||||
hb.SetPadded(true)
|
||||
|
||||
hb.Append(newBtn, false)
|
||||
hb.Append(rmBtn, false)
|
||||
hb.Append(submitBtn, false)
|
||||
hb.Append(detailBtn, false)
|
||||
|
||||
vb.Append(hb, false)
|
||||
|
||||
rth := newRouteTableHandler()
|
||||
rp := mainM.GetRoutePolicy()
|
||||
|
||||
if rp != nil {
|
||||
rpn := rp.Clone()
|
||||
rth.RoutePolicy = rpn
|
||||
}
|
||||
model := ui.NewTableModel(rth)
|
||||
|
||||
curSelectedIdx := -1
|
||||
|
||||
table := ui.NewTable(&ui.TableParams{
|
||||
Model: model,
|
||||
RowBackgroundColorModelColumn: 3,
|
||||
})
|
||||
table.AppendTextColumn("idx", 0, 0, nil)
|
||||
table.AppendTextColumn("ToTag", 1, 1, nil)
|
||||
table.AppendTextColumn("User", 2, 2, nil)
|
||||
table.AppendTextColumn("IP", 3, 3, nil)
|
||||
table.AppendTextColumn("Match", 4, 4, nil)
|
||||
table.AppendTextColumn("Domain", 5, 5, nil)
|
||||
|
||||
table.SetColumnWidth(0, 20)
|
||||
|
||||
table.OnRowClicked(func(t *ui.Table, i int) {
|
||||
curSelectedIdx = i
|
||||
})
|
||||
|
||||
vb.Append(table, false)
|
||||
|
||||
newBtn.OnClicked(func(b *ui.Button) {
|
||||
rth.List = append(rth.List, netLayer.NewFullRouteSet())
|
||||
model.RowInserted(len(rth.List) - 1)
|
||||
})
|
||||
rmBtn.OnClicked(func(b *ui.Button) {
|
||||
if curSelectedIdx < 0 || curSelectedIdx >= len(rth.List) {
|
||||
return
|
||||
}
|
||||
rth.List = utils.TrimSlice(rth.List, curSelectedIdx)
|
||||
model.RowDeleted(curSelectedIdx)
|
||||
})
|
||||
submitBtn.OnClicked(func(b *ui.Button) {
|
||||
rpn := rth.RoutePolicy.Clone()
|
||||
mainM.SetRoutePolicy(&rpn)
|
||||
mainM.PrintAllStateForHuman(os.Stdout, true)
|
||||
})
|
||||
|
||||
detailBtn.OnClicked(func(b *ui.Button) {
|
||||
ui.MsgBox(mainwin,
|
||||
"路由配置详细说明",
|
||||
`
|
||||
match 为域名匹配任意字符串,
|
||||
domain 匹配域名或子域名
|
||||
|
||||
每一项在配置文件中都可以提供数组, 而在本gui中只能配置一个.如果你编辑了原来的数组配置, 则原数据不会被保存
|
||||
`)
|
||||
})
|
||||
|
||||
return vb
|
||||
}
|
||||
|
||||
type routeTableH struct {
|
||||
netLayer.RoutePolicy
|
||||
}
|
||||
|
||||
func newRouteTableHandler() *routeTableH {
|
||||
m := new(routeTableH)
|
||||
return m
|
||||
}
|
||||
|
||||
func (rt *routeTableH) ColumnTypes(m *ui.TableModel) []ui.TableValue {
|
||||
return []ui.TableValue{
|
||||
|
||||
ui.TableString(""),
|
||||
}
|
||||
}
|
||||
|
||||
func (rt *routeTableH) NumRows(m *ui.TableModel) int {
|
||||
return len(rt.List)
|
||||
}
|
||||
|
||||
func (rt *routeTableH) CellValue(m *ui.TableModel, row, column int) ui.TableValue {
|
||||
|
||||
switch column {
|
||||
case 0:
|
||||
return ui.TableString(strconv.Itoa(row))
|
||||
case 1:
|
||||
return ui.TableString(rt.List[row].OutTag)
|
||||
case 2:
|
||||
us := rt.List[row].Users
|
||||
if len(us) > 0 {
|
||||
u := utils.GetOneFromMap(us, "")
|
||||
return ui.TableString(u)
|
||||
}
|
||||
return ui.TableString("")
|
||||
case 3:
|
||||
ips := rt.List[row].IPs
|
||||
if len(ips) > 0 {
|
||||
addr := utils.GetOneFromMap(ips, netip.Addr{})
|
||||
return ui.TableString(addr.String())
|
||||
}
|
||||
return ui.TableString("")
|
||||
|
||||
case 4:
|
||||
ms := rt.List[row].Match
|
||||
if len(ms) > 0 {
|
||||
d := ms[0]
|
||||
return ui.TableString(d)
|
||||
}
|
||||
return ui.TableString("")
|
||||
case 5:
|
||||
domains := rt.List[row].Domains
|
||||
if len(domains) > 0 {
|
||||
d := utils.GetOneFromMap(domains, "")
|
||||
return ui.TableString(d)
|
||||
}
|
||||
return ui.TableString("")
|
||||
|
||||
}
|
||||
|
||||
return ui.TableInt(0)
|
||||
}
|
||||
|
||||
func (rt *routeTableH) SetCellValue(m *ui.TableModel, row, column int, value ui.TableValue) {
|
||||
if ce := utils.CanLogDebug("gui: SetCellValue"); ce != nil {
|
||||
ce.Write(zap.Int("row", row),
|
||||
zap.Int("column", column),
|
||||
zap.Any("value", value),
|
||||
)
|
||||
}
|
||||
switch column {
|
||||
case 1:
|
||||
s := value.(ui.TableString)
|
||||
rt.List[row].OutTag = string(s)
|
||||
case 2:
|
||||
s := value.(ui.TableString)
|
||||
rt.List[row].Users = map[string]bool{string(s): true}
|
||||
case 3:
|
||||
s := value.(ui.TableString)
|
||||
|
||||
na, e := netip.ParseAddr(string(s))
|
||||
if e == nil {
|
||||
rt.List[row].IPs = map[netip.Addr]bool{na: true}
|
||||
} else {
|
||||
if ce := utils.CanLogErr("gui: ip wrong"); ce != nil {
|
||||
ce.Write(zap.Error(e),
|
||||
zap.String("value", string(s)),
|
||||
)
|
||||
}
|
||||
}
|
||||
case 4:
|
||||
s := value.(ui.TableString)
|
||||
rt.List[row].Match = []string{string(s)}
|
||||
|
||||
case 5:
|
||||
s := value.(ui.TableString)
|
||||
rt.List[row].Domains = map[string]bool{string(s): true}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,6 @@ func (i *confFileArrayFlags) Set(value string) error {
|
||||
}
|
||||
|
||||
var (
|
||||
//configFileName string
|
||||
configFiles confFileArrayFlags
|
||||
|
||||
useNativeUrlFormat bool
|
||||
@@ -66,9 +65,11 @@ const (
|
||||
func init() {
|
||||
mainM = machine.New()
|
||||
|
||||
flag.Var(&configFiles, "c", "config files; mutiple files are possible, but must all be toml files, like -c c1.toml -c c2.toml")
|
||||
|
||||
flag.IntVar(&utils.LogLevel, "ll", utils.DefaultLL, "log level,0=debug, 1=info, 2=warning, 3=error, 4=dpanic, 5=panic, 6=fatal")
|
||||
|
||||
flag.IntVar(&utils.LogLevelForFile, "llf", -1, "log level for log file,if negative, it will be the same as ll. 0=debug, 1=info, 2=warning, 3=error, 4=dpanic, 5=panic, 6=fatal")
|
||||
flag.IntVar(&utils.LogLevelForFile, "llf", -1, "log level for log file,if negative, it will be the same as ll.")
|
||||
|
||||
//有时发现在某些情况下,dns查询或者tcp链接的建立很慢,甚至超过8秒, 所以开放自定义超时时间,便于在不同环境下测试
|
||||
flag.IntVar(&dialTimeoutSecond, "dt", int(netLayer.DefaultDialTimeout/time.Second), "dial timeout, in second")
|
||||
@@ -91,8 +92,6 @@ func init() {
|
||||
flag.StringVar(&netLayer.GeositeFolder, "geosite", netLayer.DefaultGeositeFolder, "geosite folder name (set it to the relative or absolute path of your geosite/data folder)")
|
||||
flag.StringVar(&utils.ExtraSearchPath, "path", "", "search path for mmdb, geosite and other required files")
|
||||
|
||||
flag.Var(&configFiles, "c", "config files; mutiple files are possible, but must all be toml files, like -c c1.toml -c c2.toml")
|
||||
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -127,7 +127,7 @@ func (m *M) runApiServer() {
|
||||
}
|
||||
|
||||
ser.addServerHandle(mux, "allstate", func(w http.ResponseWriter, r *http.Request) {
|
||||
m.PrintAllState(w)
|
||||
m.PrintAllState(w, false)
|
||||
})
|
||||
ser.addServerHandle(mux, "hotDelete", func(w http.ResponseWriter, r *http.Request) {
|
||||
q := r.URL.Query()
|
||||
|
||||
@@ -217,7 +217,7 @@ func (m *M) SetupListenAndRoute() {
|
||||
m.LoadListenConf(m.standardConf.Listen, false)
|
||||
|
||||
if len(m.standardConf.Fallbacks) > 0 {
|
||||
m.ParseFallbacksAtSymbol(m.standardConf.Fallbacks)
|
||||
m.parseFallbacksAtSymbol(m.standardConf.Fallbacks)
|
||||
}
|
||||
|
||||
m.routingEnv = proxy.LoadEnvFromStandardConf(&m.standardConf, myCountryISO_3166)
|
||||
@@ -227,7 +227,7 @@ func (m *M) SetupDial() {
|
||||
if len(m.standardConf.Dial) < 1 && m.DefaultOutClient == nil {
|
||||
utils.Warn("no dial in config settings, will add 'direct'")
|
||||
|
||||
m.SetDefaultDirectClient()
|
||||
m.setDefaultDirectClient()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
/*
|
||||
Package machine 定义一个 可以直接运行的有限状态机;这个机器可以直接被可执行文件或者动态库所使用.
|
||||
|
||||
machine把所有运行代理所需要的代码包装起来,对外像一个黑盒子。
|
||||
|
||||
关键点是不使用任何静态变量,所有变量都放在machine中。
|
||||
Package machine 包装运行代理所需的方法,被设计为可轻易被外部程序或库调用, 而无需理解其内部细节
|
||||
*/
|
||||
package machine
|
||||
|
||||
@@ -18,6 +14,7 @@ import (
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/e1732a364fed/v2ray_simple"
|
||||
"github.com/e1732a364fed/v2ray_simple/httpLayer"
|
||||
"github.com/e1732a364fed/v2ray_simple/netLayer"
|
||||
"github.com/e1732a364fed/v2ray_simple/proxy"
|
||||
"github.com/e1732a364fed/v2ray_simple/utils"
|
||||
"go.uber.org/zap"
|
||||
@@ -118,7 +115,7 @@ func (m *M) Start() {
|
||||
for range m.stateReportTicker.C {
|
||||
sw.Prefix = []byte(time.Now().Format("2006-01-02 15:04:05.999 "))
|
||||
sw.Write([]byte("Current state:\n"))
|
||||
m.PrintAllStateForHuman(&sw)
|
||||
m.PrintAllStateForHuman(&sw, false)
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -167,7 +164,7 @@ func (m *M) Stop() {
|
||||
m.Unlock()
|
||||
}
|
||||
|
||||
func (m *M) SetDefaultDirectClient() {
|
||||
func (m *M) setDefaultDirectClient() {
|
||||
m.allClients = append(m.allClients, v2ray_simple.DirectClient)
|
||||
m.DefaultOutClient = v2ray_simple.DirectClient
|
||||
|
||||
@@ -175,7 +172,7 @@ func (m *M) SetDefaultDirectClient() {
|
||||
}
|
||||
|
||||
// 将fallback配置中的@转化成实际对应的server的地址
|
||||
func (m *M) ParseFallbacksAtSymbol(fs []*httpLayer.FallbackConf) {
|
||||
func (m *M) parseFallbacksAtSymbol(fs []*httpLayer.FallbackConf) {
|
||||
for _, fbConf := range fs {
|
||||
if fbConf.Dest == nil {
|
||||
continue
|
||||
@@ -200,7 +197,7 @@ func (m *M) HasProxyRunning() bool {
|
||||
return len(m.listenCloserList) > 0
|
||||
}
|
||||
|
||||
func (m *M) printAllState_2(w io.Writer) {
|
||||
func (m *M) printState_proxy(w io.Writer) {
|
||||
for i, s := range m.allServers {
|
||||
fmt.Fprintln(w, "inServer", i, proxy.GetVSI_url(s, ""))
|
||||
|
||||
@@ -210,7 +207,15 @@ func (m *M) printAllState_2(w io.Writer) {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *M) PrintAllState(w io.Writer) {
|
||||
func (m *M) printState_routePolicy(w io.Writer) {
|
||||
if rp := m.routingEnv.RoutePolicy; rp != nil {
|
||||
for i, v := range rp.List {
|
||||
fmt.Fprintln(w, "route", i, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *M) PrintAllState(w io.Writer, printRouteEnv bool) {
|
||||
if w == nil {
|
||||
w = os.Stdout
|
||||
}
|
||||
@@ -218,11 +223,15 @@ func (m *M) PrintAllState(w io.Writer) {
|
||||
fmt.Fprintln(w, "allDownloadBytesSinceStart", m.AllDownloadBytesSinceStart)
|
||||
fmt.Fprintln(w, "allUploadBytesSinceStart", m.AllUploadBytesSinceStart)
|
||||
|
||||
m.printAllState_2(w)
|
||||
m.printState_proxy(w)
|
||||
if printRouteEnv {
|
||||
m.printState_routePolicy(w)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// mimic PrintAllState
|
||||
func (m *M) PrintAllStateForHuman(w io.Writer) {
|
||||
func (m *M) PrintAllStateForHuman(w io.Writer, printRouteEnv bool) {
|
||||
if w == nil {
|
||||
w = os.Stdout
|
||||
}
|
||||
@@ -230,6 +239,18 @@ func (m *M) PrintAllStateForHuman(w io.Writer) {
|
||||
fmt.Fprintln(w, "allDownloadBytesSinceStart", humanize.Bytes(m.AllDownloadBytesSinceStart))
|
||||
fmt.Fprintln(w, "allUploadBytesSinceStart", humanize.Bytes(m.AllUploadBytesSinceStart))
|
||||
|
||||
m.printAllState_2(w)
|
||||
m.printState_proxy(w)
|
||||
if printRouteEnv {
|
||||
m.printState_routePolicy(w)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (m *M) GetRoutePolicy() *netLayer.RoutePolicy {
|
||||
return m.routingEnv.RoutePolicy
|
||||
}
|
||||
|
||||
func (m *M) SetRoutePolicy(rp *netLayer.RoutePolicy) {
|
||||
m.routingEnv.RoutePolicy = rp
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/yl2chen/cidranger"
|
||||
"golang.org/x/exp/maps"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// 用于 HasFullOrSubDomain函数
|
||||
@@ -50,14 +52,16 @@ type TargetDescription struct {
|
||||
UserIdentityStr string
|
||||
}
|
||||
|
||||
// Set 是 “集合” 的意思, 是一组相同类型的数据放到一起。
|
||||
//
|
||||
// 这里的相同点,就是它们同属于 将发往一个方向, 即同属一个路由策略。
|
||||
//
|
||||
// 任意一个参数匹配后,都将发往相同的方向,由该方向OutTag 指定。
|
||||
// RouteSet 只负责把一些属性相同的 “网络层/传输层 特征” 放到一起。
|
||||
//
|
||||
// 这里主要通过 ip,域名 和 inTag 进行分流。域名的匹配又分多种方式。
|
||||
/*
|
||||
RouteSet 只负责把一些属性相同的 “网络层/传输层 特征” 放到一起。
|
||||
|
||||
Set 是 “集合” 的意思, 是一组相同类型的数据放到一起。
|
||||
|
||||
这里的相同点,就是它们同属于 将发往一个方向, 即同属一个路由策略。
|
||||
|
||||
任意一个网络参数匹配后,都将发往相同的方向,由该方向OutTag 指定。
|
||||
若还给出了 InTags, Users 或 传输层, 则这些条件都通过后, 才进行网络层判断.
|
||||
*/
|
||||
type RouteSet struct {
|
||||
//网络层
|
||||
NetRanger cidranger.Ranger //一个范围
|
||||
@@ -273,6 +277,35 @@ func (rs *RouteSet) IsAddrIn(a Addr) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (rs *RouteSet) Clone() (newOne *RouteSet) {
|
||||
newOne = &RouteSet{
|
||||
NetRanger: cidranger.NewPCTrieRanger(),
|
||||
IPs: maps.Clone(rs.IPs),
|
||||
Match: slices.Clone(rs.Match),
|
||||
Domains: maps.Clone(rs.Domains),
|
||||
Full: maps.Clone(rs.Full),
|
||||
Users: maps.Clone(rs.Users),
|
||||
Geosites: slices.Clone(rs.Geosites),
|
||||
InTags: maps.Clone(rs.InTags),
|
||||
OutTags: slices.Clone(rs.OutTags),
|
||||
OutTag: rs.OutTag,
|
||||
Regex: slices.Clone(rs.Regex),
|
||||
Countries: maps.Clone(rs.Countries),
|
||||
AllowedTransportLayerProtocols: rs.AllowedTransportLayerProtocols,
|
||||
}
|
||||
|
||||
entries, _ := newOne.NetRanger.CoveredNetworks(*cidranger.AllIPv4)
|
||||
for _, v := range entries {
|
||||
newOne.NetRanger.Insert(v)
|
||||
}
|
||||
ip6entries, _ := newOne.NetRanger.CoveredNetworks(*cidranger.AllIPv6)
|
||||
for _, v := range ip6entries {
|
||||
newOne.NetRanger.Insert(v)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 一个完整的 所有RouteSet的列表,进行路由时,直接遍历即可。
|
||||
// 所谓的路由实际上就是分流。
|
||||
type RoutePolicy struct {
|
||||
@@ -291,6 +324,13 @@ func (rp *RoutePolicy) AddRouteSet(rs *RouteSet) {
|
||||
}
|
||||
}
|
||||
|
||||
func (rp *RoutePolicy) Clone() (newOne RoutePolicy) {
|
||||
for _, v := range rp.List {
|
||||
newOne.List = append(newOne.List, v.Clone())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 根据td 以及 RoutePolicy的配置 计算出 一个 对应的 proxy.Client 的 tag。
|
||||
// 默认情况下,始终具有direct这个tag以及 proxy这个tag,无需用户额外在配置文件中指定。
|
||||
// 默认如果不匹配任何值的话,就会流向 "proxy" tag,也就是客户设置的 remoteClient的值。
|
||||
|
||||
@@ -9,6 +9,15 @@ import (
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
func GetOneFromMap[T comparable](m map[T]bool, defaultV T) T {
|
||||
for k := range m {
|
||||
return k
|
||||
}
|
||||
return defaultV
|
||||
}
|
||||
|
||||
// generics ////////////////////////////////////////////////////////////////
|
||||
|
||||
func ArrayToPtrArray[T any](a []T) (r []*T) {
|
||||
for _, v := range a {
|
||||
r = append(r, &v)
|
||||
@@ -61,8 +70,6 @@ func AllSubSets_improve1[T comparable](set []T) (subsets [][]T) {
|
||||
return subsets
|
||||
}
|
||||
|
||||
// generics ////////////////////////////////////////////////////////////////
|
||||
|
||||
func CloneSlice[T any](a []T) (r []T) {
|
||||
r = make([]T, len(a))
|
||||
copy(r, a)
|
||||
@@ -82,7 +89,7 @@ func TrimSlice[T any](a []T, deleteIndex int) []T {
|
||||
}
|
||||
return a[:j]
|
||||
|
||||
//实际上 golang.org/x/exp/slices 的 Delete 函数也可以
|
||||
//实际上 golang.org/x/exp/slices 的 Delete 函数也可以, 但是它是范围删除, 多了一个参数
|
||||
}
|
||||
|
||||
// 根据传入的order来对arr重新排序;order必须长度与arr一致,而且包含所有索引
|
||||
|
||||
Reference in New Issue
Block a user