mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-05 20:54:14 +00:00
148 lines
3.7 KiB
Go
148 lines
3.7 KiB
Go
|
|
package service
|
||
|
|
|
||
|
|
import (
|
||
|
|
"fmt"
|
||
|
|
"strings"
|
||
|
|
|
||
|
|
"github.com/mhsanaei/3x-ui/v3/database"
|
||
|
|
"github.com/mhsanaei/3x-ui/v3/database/model"
|
||
|
|
|
||
|
|
"gorm.io/gorm"
|
||
|
|
)
|
||
|
|
|
||
|
|
type FallbackService struct{}
|
||
|
|
|
||
|
|
// FallbackInput is the payload shape POSTed by the inbound form.
|
||
|
|
type FallbackInput struct {
|
||
|
|
ChildId int `json:"childId"`
|
||
|
|
Name string `json:"name"`
|
||
|
|
Alpn string `json:"alpn"`
|
||
|
|
Path string `json:"path"`
|
||
|
|
Xver int `json:"xver"`
|
||
|
|
SortOrder int `json:"sortOrder"`
|
||
|
|
}
|
||
|
|
|
||
|
|
// GetByMaster returns every fallback rule attached to the master inbound.
|
||
|
|
func (s *FallbackService) GetByMaster(masterId int) ([]model.InboundFallback, error) {
|
||
|
|
var rows []model.InboundFallback
|
||
|
|
err := database.GetDB().
|
||
|
|
Where("master_id = ?", masterId).
|
||
|
|
Order("sort_order ASC, id ASC").
|
||
|
|
Find(&rows).Error
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
return rows, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// GetParentForChild finds the first fallback rule that points at childId.
|
||
|
|
// Used by client-link generation: when a child inbound is attached as a
|
||
|
|
// fallback, its client links should advertise the master's address+port
|
||
|
|
// and TLS instead of the child's loopback listen.
|
||
|
|
func (s *FallbackService) GetParentForChild(childId int) (*model.InboundFallback, error) {
|
||
|
|
var row model.InboundFallback
|
||
|
|
err := database.GetDB().
|
||
|
|
Where("child_id = ?", childId).
|
||
|
|
Order("sort_order ASC, id ASC").
|
||
|
|
First(&row).Error
|
||
|
|
if err == gorm.ErrRecordNotFound {
|
||
|
|
return nil, nil
|
||
|
|
}
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
return &row, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// SetByMaster replaces the master's entire fallback list atomically.
|
||
|
|
func (s *FallbackService) SetByMaster(masterId int, items []FallbackInput) error {
|
||
|
|
db := database.GetDB()
|
||
|
|
return db.Transaction(func(tx *gorm.DB) error {
|
||
|
|
if err := tx.Where("master_id = ?", masterId).Delete(&model.InboundFallback{}).Error; err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
for i, c := range items {
|
||
|
|
if c.ChildId <= 0 || c.ChildId == masterId {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
row := model.InboundFallback{
|
||
|
|
MasterId: masterId,
|
||
|
|
ChildId: c.ChildId,
|
||
|
|
Name: c.Name,
|
||
|
|
Alpn: c.Alpn,
|
||
|
|
Path: c.Path,
|
||
|
|
Xver: c.Xver,
|
||
|
|
SortOrder: c.SortOrder,
|
||
|
|
}
|
||
|
|
if row.SortOrder == 0 {
|
||
|
|
row.SortOrder = i
|
||
|
|
}
|
||
|
|
if err := tx.Create(&row).Error; err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
// BuildFallbacksJSON resolves the master's fallback rows into Xray's
|
||
|
|
// expected settings.fallbacks shape, looking up each child's listen+port
|
||
|
|
// to fill the dest field. Returns nil when the master has no rules.
|
||
|
|
func (s *FallbackService) BuildFallbacksJSON(tx *gorm.DB, masterId int) ([]map[string]any, error) {
|
||
|
|
if tx == nil {
|
||
|
|
tx = database.GetDB()
|
||
|
|
}
|
||
|
|
var rows []model.InboundFallback
|
||
|
|
err := tx.Where("master_id = ?", masterId).
|
||
|
|
Order("sort_order ASC, id ASC").
|
||
|
|
Find(&rows).Error
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
if len(rows) == 0 {
|
||
|
|
return nil, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
childIds := make([]int, 0, len(rows))
|
||
|
|
for i := range rows {
|
||
|
|
childIds = append(childIds, rows[i].ChildId)
|
||
|
|
}
|
||
|
|
var children []model.Inbound
|
||
|
|
if err := tx.Where("id IN ?", childIds).Find(&children).Error; err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
byId := make(map[int]*model.Inbound, len(children))
|
||
|
|
for i := range children {
|
||
|
|
byId[children[i].Id] = &children[i]
|
||
|
|
}
|
||
|
|
|
||
|
|
out := make([]map[string]any, 0, len(rows))
|
||
|
|
for _, r := range rows {
|
||
|
|
child, ok := byId[r.ChildId]
|
||
|
|
if !ok {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
listen := strings.TrimSpace(child.Listen)
|
||
|
|
if listen == "" || listen == "0.0.0.0" || listen == "::" || listen == "::0" {
|
||
|
|
listen = "127.0.0.1"
|
||
|
|
}
|
||
|
|
entry := map[string]any{
|
||
|
|
"dest": fmt.Sprintf("%s:%d", listen, child.Port),
|
||
|
|
}
|
||
|
|
if r.Name != "" {
|
||
|
|
entry["name"] = r.Name
|
||
|
|
}
|
||
|
|
if r.Alpn != "" {
|
||
|
|
entry["alpn"] = r.Alpn
|
||
|
|
}
|
||
|
|
if r.Path != "" {
|
||
|
|
entry["path"] = r.Path
|
||
|
|
}
|
||
|
|
if r.Xver > 0 {
|
||
|
|
entry["xver"] = r.Xver
|
||
|
|
}
|
||
|
|
out = append(out, entry)
|
||
|
|
}
|
||
|
|
return out, nil
|
||
|
|
}
|