mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-10-26 07:44:59 +03:00
8eb58a4a14
The access-log scanner and the cross-node merge stored the same IP list in opposite orders, so neither saw a row as unchanged, and the merge re-read every preloaded row and re-upserted every attribution row each sync cycle. All writers now share one canonical encoder (timestamp, then IP ascending), the first CAS is seeded from the preload, and identical attribution rows are skipped. A create race between the scanner and node sync now merges into the winning row instead of dropping the loser's IPs, which on PostgreSQL also aborted the scanner's whole scan transaction. Closes #6927
49 lines
1.7 KiB
Go
49 lines
1.7 KiB
Go
package model
|
|
|
|
import (
|
|
"cmp"
|
|
"encoding/json"
|
|
"slices"
|
|
)
|
|
|
|
// ClientIpEntry records an observed client IP and its last-seen Unix timestamp.
|
|
type ClientIpEntry struct {
|
|
IP string `json:"ip"`
|
|
Timestamp int64 `json:"timestamp"`
|
|
}
|
|
|
|
func SortClientIpEntries(entries []ClientIpEntry) {
|
|
slices.SortFunc(entries, func(a, b ClientIpEntry) int {
|
|
if order := cmp.Compare(a.Timestamp, b.Timestamp); order != 0 {
|
|
return order
|
|
}
|
|
return cmp.Compare(a.IP, b.IP)
|
|
})
|
|
}
|
|
|
|
func MarshalClientIpEntries(entries []ClientIpEntry) (string, error) {
|
|
if len(entries) == 0 {
|
|
return "[]", nil
|
|
}
|
|
SortClientIpEntries(entries)
|
|
encoded, err := json.Marshal(entries)
|
|
return string(encoded), err
|
|
}
|
|
|
|
// NodeClientIp records which panel (identified by its stable panelGuid) observed
|
|
// a client's IPs on its own Xray. Unlike InboundClientIps (a flattened,
|
|
// cluster-wide union used for IP-limit counting and that is pushed back to every
|
|
// node), this table preserves attribution: it never mixes in IPs a parent pushed
|
|
// down, so the master can tell exactly which node a given IP is connecting to.
|
|
//
|
|
// Rows under the local panel's own panelGuid are written by check_client_ip_job
|
|
// from local Xray observations; rows under remote guids are merged in by the node
|
|
// sync job from each node's clientIpsByGuid report (its own panelGuid subtree plus
|
|
// any descendants), so attribution survives across a chain of nodes.
|
|
type NodeClientIp struct {
|
|
Id int `json:"id" gorm:"primaryKey;autoIncrement"`
|
|
NodeGuid string `json:"nodeGuid" gorm:"uniqueIndex:idx_nodeip_guid_email,priority:1;not null"`
|
|
Email string `json:"email" gorm:"uniqueIndex:idx_nodeip_guid_email,priority:2;not null"`
|
|
Ips string `json:"ips"`
|
|
}
|