mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-06 05:05:01 +03:00
fix(tgbot): look up a Telegram account's clients through the indexed client table (#6887)
Every bot usage command and the hourly report resolved a Telegram ID by expanding every inbound's settings.clients, re-parsing the matched inbounds and looking up each client again by email. With thousands of clients this ran thousands of times per report and showed up as high PostgreSQL CPU, and an account whose lookup came back empty was retried once per client carrying it. The lookup now uses the normalized clients table and its Telegram-ID index in one query per account, and the hourly pass checks each account once. Bot output and thresholds are unchanged; link callbacks now authorize against the current client records instead of possibly stale settings JSON.
This commit is contained in:
@@ -7,7 +7,6 @@ import (
|
||||
"fmt"
|
||||
"maps"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -1057,72 +1056,37 @@ func (s *InboundService) DelDepletedClients(id int) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ClientsTable migrates legacy JSON at startup; normal writes synchronize these
|
||||
// records. Never fall back to settings JSON, which can retain stale ownership.
|
||||
func (s *InboundService) GetClientTrafficTgBot(tgId int64) ([]*xray.ClientTraffic, error) {
|
||||
db := database.GetDB()
|
||||
|
||||
idQuery := fmt.Sprintf(
|
||||
"SELECT DISTINCT inbounds.id %s WHERE %s = ?",
|
||||
database.JSONClientsFromInbound(),
|
||||
database.JSONFieldText("client.value", "tgId"),
|
||||
)
|
||||
var inboundIds []int
|
||||
if err := db.Raw(idQuery, strconv.FormatInt(tgId, 10)).Scan(&inboundIds).Error; err != nil {
|
||||
logger.Errorf("Error retrieving inbounds with tgId %d: %v", tgId, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var inbounds []*model.Inbound
|
||||
if len(inboundIds) > 0 {
|
||||
err := db.Model(model.Inbound{}).Where("id IN ?", inboundIds).Find(&inbounds).Error
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
logger.Errorf("Error retrieving inbounds with tgId %d: %v", tgId, err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var emails []string
|
||||
for _, inbound := range inbounds {
|
||||
clients, err := s.GetClients(inbound)
|
||||
if err != nil {
|
||||
logger.Errorf("Error retrieving clients for inbound %d: %v", inbound.Id, err)
|
||||
continue
|
||||
}
|
||||
for _, client := range clients {
|
||||
if client.TgID == tgId {
|
||||
emails = append(emails, client.Email)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Chunked to stay under SQLite's bind-variable limit when a single Telegram
|
||||
// account owns thousands of clients across inbounds.
|
||||
uniqEmails := uniqueNonEmptyStrings(emails)
|
||||
traffics := make([]*xray.ClientTraffic, 0, len(uniqEmails))
|
||||
for _, batch := range chunkStrings(uniqEmails, sqliteMaxVars) {
|
||||
var page []*xray.ClientTraffic
|
||||
if err := db.Model(xray.ClientTraffic{}).Where("email IN ?", batch).Find(&page).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
continue
|
||||
}
|
||||
logger.Errorf("Error retrieving ClientTraffic for emails %v: %v", batch, err)
|
||||
return nil, err
|
||||
}
|
||||
traffics = append(traffics, page...)
|
||||
}
|
||||
if len(traffics) == 0 {
|
||||
logger.Warning("No ClientTraffic records found for emails:", emails)
|
||||
if tgId <= 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Populate UUID and other client data for each traffic record
|
||||
for i := range traffics {
|
||||
if ct, client, e := s.GetClientByEmail(traffics[i].Email); e == nil && ct != nil && client != nil {
|
||||
traffics[i].Enable = client.Enable
|
||||
traffics[i].UUID = client.ID
|
||||
traffics[i].SubId = client.SubID
|
||||
}
|
||||
type row struct {
|
||||
xray.ClientTraffic
|
||||
ClientEnable bool
|
||||
ClientUUID string
|
||||
ClientSubID string
|
||||
}
|
||||
var rows []row
|
||||
err := database.GetDB().Table("clients").
|
||||
Select("client_traffics.*, clients.enable AS client_enable, clients.uuid AS client_uuid, clients.sub_id AS client_sub_id").
|
||||
Joins("JOIN client_traffics ON client_traffics.email = clients.email").
|
||||
Where("clients.tg_id = ?", tgId).
|
||||
Where("EXISTS (SELECT 1 FROM client_inbounds JOIN inbounds ON inbounds.id = client_inbounds.inbound_id WHERE client_inbounds.client_id = clients.id)").
|
||||
Order("clients.id ASC").
|
||||
Find(&rows).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
traffics := make([]*xray.ClientTraffic, 0, len(rows))
|
||||
for i := range rows {
|
||||
traffic := &rows[i].ClientTraffic
|
||||
traffic.Enable = rows[i].ClientEnable
|
||||
traffic.UUID = rows[i].ClientUUID
|
||||
traffic.SubId = rows[i].ClientSubID
|
||||
traffics = append(traffics, traffic)
|
||||
}
|
||||
|
||||
return traffics, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user