feat(clients): make clients+client_inbounds the runtime source of truth

Adds ClientService.SyncInbound that reconciles the new tables from
each inbound's clients list whenever existing service paths mutate
settings.clients. Wires it into AddInbound, UpdateInbound,
AddInboundClient, UpdateInboundClient, DelInboundClient,
DelInboundClientByEmail, DelDepletedClients, autoRenewClients, and
the timestamp-backfill path in adjustTraffics, plus DetachInbound
on DelInbound.

GetXrayConfig now builds settings.clients from the new tables before
writing config.json, and getInboundsBySubId joins through them
instead of JSON_EACH on settings JSON. Live Xray config and
subscription endpoints are now driven by the relational view;
settings.clients JSON stays in step as a side effect of every write.
This commit is contained in:
MHSanaei
2026-05-17 07:15:16 +02:00
parent c251482f26
commit ba3c581372
4 changed files with 297 additions and 54 deletions
+5 -6
View File
@@ -144,15 +144,14 @@ func (s *SubService) GetSubs(subId string, host string) ([]string, int64, xray.C
func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error) {
db := database.GetDB()
var inbounds []*model.Inbound
// allow "hysteria2" so imports stored with the literal v2 protocol
// string still surface here (#4081)
err := db.Model(model.Inbound{}).Preload("ClientStats").Where(`id in (
SELECT DISTINCT inbounds.id
FROM inbounds,
JSON_EACH(JSON_EXTRACT(inbounds.settings, '$.clients')) AS client
FROM inbounds
JOIN client_inbounds ON client_inbounds.inbound_id = inbounds.id
JOIN clients ON clients.id = client_inbounds.client_id
WHERE
protocol in ('vmess','vless','trojan','shadowsocks','hysteria','hysteria2')
AND JSON_EXTRACT(client.value, '$.subId') = ? AND enable = ?
inbounds.protocol in ('vmess','vless','trojan','shadowsocks','hysteria','hysteria2')
AND clients.sub_id = ? AND inbounds.enable = ?
)`, subId, true).Find(&inbounds).Error
if err != nil {
return nil, err