client_lookup.go 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. package service
  2. import (
  3. "encoding/json"
  4. "strings"
  5. "github.com/mhsanaei/3x-ui/v3/internal/database"
  6. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  7. "github.com/mhsanaei/3x-ui/v3/internal/xray"
  8. "gorm.io/gorm"
  9. )
  10. func (s *ClientService) GetRecordByEmail(tx *gorm.DB, email string) (*model.ClientRecord, error) {
  11. if tx == nil {
  12. tx = database.GetDB()
  13. }
  14. row := &model.ClientRecord{}
  15. err := tx.Where("email = ?", email).First(row).Error
  16. if err != nil {
  17. return nil, err
  18. }
  19. return row, nil
  20. }
  21. // EffectiveFlow returns the client's flow from the first flow-capable inbound
  22. // it is attached to (lowest inbound_id with a non-empty flow_override). The
  23. // canonical clients.Flow column is unreliable for multi-inbound clients: a
  24. // non-flow inbound (Hysteria, WS, gRPC, …) carries an empty flow and, when its
  25. // SyncInbound runs last, overwrites the column to "" even though a VLESS Reality
  26. // inbound stored a real flow. The per-inbound flow_override is always correct,
  27. // so derive the display flow from it (order-independent). See issue #4792.
  28. func (s *ClientService) EffectiveFlow(tx *gorm.DB, recordId int) (string, error) {
  29. if tx == nil {
  30. tx = database.GetDB()
  31. }
  32. var flows []string
  33. err := tx.Model(&model.ClientInbound{}).
  34. Where("client_id = ? AND flow_override <> ?", recordId, "").
  35. Order("inbound_id ASC").
  36. Limit(1).
  37. Pluck("flow_override", &flows).Error
  38. if err != nil {
  39. return "", err
  40. }
  41. if len(flows) == 0 {
  42. return "", nil
  43. }
  44. return flows[0], nil
  45. }
  46. func (s *ClientService) GetInboundIdsForEmail(tx *gorm.DB, email string) ([]int, error) {
  47. if tx == nil {
  48. tx = database.GetDB()
  49. }
  50. var ids []int
  51. err := tx.Table("client_inbounds").
  52. Select("client_inbounds.inbound_id").
  53. Joins("JOIN clients ON clients.id = client_inbounds.client_id").
  54. Where("clients.email = ?", email).
  55. Scan(&ids).Error
  56. if err != nil {
  57. return nil, err
  58. }
  59. return ids, nil
  60. }
  61. func (s *ClientService) GetByID(id int) (*model.ClientRecord, error) {
  62. row := &model.ClientRecord{}
  63. if err := database.GetDB().Where("id = ?", id).First(row).Error; err != nil {
  64. return nil, err
  65. }
  66. return row, nil
  67. }
  68. func (s *ClientService) GetInboundIdsForRecord(id int) ([]int, error) {
  69. var ids []int
  70. err := database.GetDB().Table("client_inbounds").
  71. Where("client_id = ?", id).
  72. Order("inbound_id ASC").
  73. Pluck("inbound_id", &ids).Error
  74. if err != nil {
  75. return nil, err
  76. }
  77. return ids, nil
  78. }
  79. func (s *ClientService) List() ([]ClientWithAttachments, error) {
  80. db := database.GetDB()
  81. var rows []model.ClientRecord
  82. if err := db.Order("id ASC").Find(&rows).Error; err != nil {
  83. return nil, err
  84. }
  85. if len(rows) == 0 {
  86. return []ClientWithAttachments{}, nil
  87. }
  88. clientIds := make([]int, 0, len(rows))
  89. emails := make([]string, 0, len(rows))
  90. for i := range rows {
  91. clientIds = append(clientIds, rows[i].Id)
  92. if rows[i].Email != "" {
  93. emails = append(emails, rows[i].Email)
  94. }
  95. }
  96. attachments := make(map[int][]int, len(rows))
  97. for _, batch := range chunkInts(clientIds, sqlInChunk) {
  98. var links []model.ClientInbound
  99. if err := db.Where("client_id IN ?", batch).Find(&links).Error; err != nil {
  100. return nil, err
  101. }
  102. for _, l := range links {
  103. attachments[l.ClientId] = append(attachments[l.ClientId], l.InboundId)
  104. }
  105. }
  106. trafficByEmail := make(map[string]*xray.ClientTraffic, len(emails))
  107. if len(emails) > 0 {
  108. var stats []xray.ClientTraffic
  109. for _, batch := range chunkStrings(emails, sqlInChunk) {
  110. var batchStats []xray.ClientTraffic
  111. if err := db.Where("email IN ?", batch).Find(&batchStats).Error; err != nil {
  112. return nil, err
  113. }
  114. stats = append(stats, batchStats...)
  115. }
  116. for i := range stats {
  117. trafficByEmail[stats[i].Email] = &stats[i]
  118. }
  119. }
  120. out := make([]ClientWithAttachments, 0, len(rows))
  121. for i := range rows {
  122. out = append(out, ClientWithAttachments{
  123. ClientRecord: rows[i],
  124. InboundIds: attachments[rows[i].Id],
  125. Traffic: trafficByEmail[rows[i].Email],
  126. })
  127. }
  128. return out, nil
  129. }
  130. func (s *ClientService) HasPendingNode(inboundSvc *InboundService, email string) bool {
  131. if strings.TrimSpace(email) == "" {
  132. return false
  133. }
  134. ids, err := s.GetInboundIdsForEmail(nil, email)
  135. if err != nil {
  136. return false
  137. }
  138. return inboundSvc.AnyNodePending(ids)
  139. }
  140. // findInboundIdsByClientEmail returns every inbound whose settings.clients[]
  141. // JSON contains an entry with the given email. Driver-portable (no JSON
  142. // operators) by parsing in Go — fine for the rare fallback path.
  143. func (s *ClientService) findInboundIdsByClientEmail(email string) ([]int, error) {
  144. var inbounds []model.Inbound
  145. if err := database.GetDB().
  146. Select("id, settings").
  147. Where("settings LIKE ?", "%"+email+"%").
  148. Find(&inbounds).Error; err != nil {
  149. return nil, err
  150. }
  151. out := make([]int, 0, len(inbounds))
  152. for _, ib := range inbounds {
  153. var settings map[string]any
  154. if err := json.Unmarshal([]byte(ib.Settings), &settings); err != nil {
  155. continue
  156. }
  157. clients, ok := settings["clients"].([]any)
  158. if !ok {
  159. continue
  160. }
  161. for _, c := range clients {
  162. cm, ok := c.(map[string]any)
  163. if !ok {
  164. continue
  165. }
  166. if cEmail, _ := cm["email"].(string); cEmail == email {
  167. out = append(out, ib.Id)
  168. break
  169. }
  170. }
  171. }
  172. return out, nil
  173. }