fallback.go 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. package service
  2. import (
  3. "fmt"
  4. "strings"
  5. "github.com/mhsanaei/3x-ui/v3/database"
  6. "github.com/mhsanaei/3x-ui/v3/database/model"
  7. "gorm.io/gorm"
  8. )
  9. type FallbackService struct{}
  10. // FallbackInput is the payload shape POSTed by the inbound form.
  11. type FallbackInput struct {
  12. ChildId int `json:"childId"`
  13. Name string `json:"name"`
  14. Alpn string `json:"alpn"`
  15. Path string `json:"path"`
  16. Xver int `json:"xver"`
  17. SortOrder int `json:"sortOrder"`
  18. }
  19. // GetByMaster returns every fallback rule attached to the master inbound.
  20. func (s *FallbackService) GetByMaster(masterId int) ([]model.InboundFallback, error) {
  21. var rows []model.InboundFallback
  22. err := database.GetDB().
  23. Where("master_id = ?", masterId).
  24. Order("sort_order ASC, id ASC").
  25. Find(&rows).Error
  26. if err != nil {
  27. return nil, err
  28. }
  29. return rows, nil
  30. }
  31. // GetParentForChild finds the first fallback rule that points at childId.
  32. // Used by client-link generation: when a child inbound is attached as a
  33. // fallback, its client links should advertise the master's address+port
  34. // and TLS instead of the child's loopback listen.
  35. func (s *FallbackService) GetParentForChild(childId int) (*model.InboundFallback, error) {
  36. var row model.InboundFallback
  37. err := database.GetDB().
  38. Where("child_id = ?", childId).
  39. Order("sort_order ASC, id ASC").
  40. First(&row).Error
  41. if err == gorm.ErrRecordNotFound {
  42. return nil, nil
  43. }
  44. if err != nil {
  45. return nil, err
  46. }
  47. return &row, nil
  48. }
  49. // SetByMaster replaces the master's entire fallback list atomically.
  50. func (s *FallbackService) SetByMaster(masterId int, items []FallbackInput) error {
  51. db := database.GetDB()
  52. return db.Transaction(func(tx *gorm.DB) error {
  53. if err := tx.Where("master_id = ?", masterId).Delete(&model.InboundFallback{}).Error; err != nil {
  54. return err
  55. }
  56. for i, c := range items {
  57. if c.ChildId <= 0 || c.ChildId == masterId {
  58. continue
  59. }
  60. row := model.InboundFallback{
  61. MasterId: masterId,
  62. ChildId: c.ChildId,
  63. Name: c.Name,
  64. Alpn: c.Alpn,
  65. Path: c.Path,
  66. Xver: c.Xver,
  67. SortOrder: c.SortOrder,
  68. }
  69. if row.SortOrder == 0 {
  70. row.SortOrder = i
  71. }
  72. if err := tx.Create(&row).Error; err != nil {
  73. return err
  74. }
  75. }
  76. return nil
  77. })
  78. }
  79. // BuildFallbacksJSON resolves the master's fallback rows into Xray's
  80. // expected settings.fallbacks shape, looking up each child's listen+port
  81. // to fill the dest field. Returns nil when the master has no rules.
  82. func (s *FallbackService) BuildFallbacksJSON(tx *gorm.DB, masterId int) ([]map[string]any, error) {
  83. if tx == nil {
  84. tx = database.GetDB()
  85. }
  86. var rows []model.InboundFallback
  87. err := tx.Where("master_id = ?", masterId).
  88. Order("sort_order ASC, id ASC").
  89. Find(&rows).Error
  90. if err != nil {
  91. return nil, err
  92. }
  93. if len(rows) == 0 {
  94. return nil, nil
  95. }
  96. childIds := make([]int, 0, len(rows))
  97. for i := range rows {
  98. childIds = append(childIds, rows[i].ChildId)
  99. }
  100. var children []model.Inbound
  101. if err := tx.Where("id IN ?", childIds).Find(&children).Error; err != nil {
  102. return nil, err
  103. }
  104. byId := make(map[int]*model.Inbound, len(children))
  105. for i := range children {
  106. byId[children[i].Id] = &children[i]
  107. }
  108. out := make([]map[string]any, 0, len(rows))
  109. for _, r := range rows {
  110. child, ok := byId[r.ChildId]
  111. if !ok {
  112. continue
  113. }
  114. listen := strings.TrimSpace(child.Listen)
  115. if listen == "" || listen == "0.0.0.0" || listen == "::" || listen == "::0" {
  116. listen = "127.0.0.1"
  117. }
  118. entry := map[string]any{
  119. "dest": fmt.Sprintf("%s:%d", listen, child.Port),
  120. }
  121. if r.Name != "" {
  122. entry["name"] = r.Name
  123. }
  124. if r.Alpn != "" {
  125. entry["alpn"] = r.Alpn
  126. }
  127. if r.Path != "" {
  128. entry["path"] = r.Path
  129. }
  130. if r.Xver > 0 {
  131. entry["xver"] = r.Xver
  132. }
  133. out = append(out, entry)
  134. }
  135. return out, nil
  136. }