1
0

fallback.go 3.7 KB

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