fallback.go 3.8 KB

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