Files
s-ui/backend/service/client.go
T
2025-01-18 10:55:22 +01:00

372 lines
9.3 KiB
Go

package service
import (
"encoding/json"
"s-ui/database"
"s-ui/database/model"
"s-ui/logger"
"s-ui/util"
"s-ui/util/common"
"strings"
"time"
"gorm.io/gorm"
)
type ClientService struct {
InboundService
}
func (s *ClientService) Get(id string) (*[]model.Client, error) {
if id == "" {
return s.GetAll()
}
return s.getById(id)
}
func (s *ClientService) getById(id string) (*[]model.Client, error) {
db := database.GetDB()
var client []model.Client
err := db.Model(model.Client{}).Where("id in ?", strings.Split(id, ",")).Scan(&client).Error
if err != nil {
return nil, err
}
return &client, nil
}
func (s *ClientService) GetAll() (*[]model.Client, error) {
db := database.GetDB()
var clients []model.Client
err := db.Model(model.Client{}).Select("`id`, `enable`, `name`, `desc`, `group`, `inbounds`, `up`, `down`, `volume`, `expiry`").Scan(&clients).Error
if err != nil {
return nil, err
}
return &clients, nil
}
func (s *ClientService) Save(tx *gorm.DB, act string, data json.RawMessage, hostname string) ([]uint, error) {
var err error
var inboundIds []uint
switch act {
case "new", "edit":
var client model.Client
err = json.Unmarshal(data, &client)
if err != nil {
return nil, err
}
err = json.Unmarshal(client.Inbounds, &inboundIds)
if err != nil {
return nil, err
}
err = s.updateLinksWithFixedInbounds(tx, []*model.Client{&client}, inboundIds, hostname)
if err != nil {
return nil, err
}
err = tx.Save(&client).Error
if err != nil {
return nil, err
}
case "addbulk":
var clients []*model.Client
err = json.Unmarshal(data, &clients)
if err != nil {
return nil, err
}
err = json.Unmarshal(clients[0].Inbounds, &inboundIds)
if err != nil {
return nil, err
}
err = s.updateLinksWithFixedInbounds(tx, clients, inboundIds, hostname)
if err != nil {
return nil, err
}
err = tx.Save(clients).Error
if err != nil {
return nil, err
}
case "del":
var id uint
err = json.Unmarshal(data, &id)
if err != nil {
return nil, err
}
var client model.Client
err = tx.Where("id = ?", id).First(&client).Error
if err != nil {
return nil, err
}
err = json.Unmarshal(client.Inbounds, &inboundIds)
if err != nil {
return nil, err
}
err = tx.Where("id = ?", id).Delete(model.Client{}).Error
if err != nil {
return nil, err
}
default:
return nil, common.NewErrorf("unknown action: %s", act)
}
return inboundIds, nil
}
func (s *ClientService) updateLinksWithFixedInbounds(tx *gorm.DB, clients []*model.Client, inbounIds []uint, hostname string) error {
var err error
var inbounds []model.Inbound
// Zero inbounds means removing local links only
if len(inbounIds) > 0 {
err = tx.Model(model.Inbound{}).Preload("Tls").Where("id in ? and type in ?", inbounIds, util.InboundTypeWithLink).Find(&inbounds).Error
if err != nil {
return err
}
}
for index, client := range clients {
var clientLinks []map[string]string
err = json.Unmarshal(client.Links, &clientLinks)
if err != nil {
return err
}
newClientLinks := []map[string]string{}
for _, inbound := range inbounds {
newLinks := util.LinkGenerator(client.Config, &inbound, hostname)
for _, newLink := range newLinks {
newClientLinks = append(newClientLinks, map[string]string{
"remark": inbound.Tag,
"type": "local",
"uri": newLink,
})
}
}
// Add no local links
for _, clientLink := range clientLinks {
if clientLink["type"] != "local" {
newClientLinks = append(newClientLinks, clientLink)
}
}
clients[index].Links, err = json.MarshalIndent(newClientLinks, "", " ")
if err != nil {
return err
}
}
return nil
}
func (s *ClientService) UpdateClientsOnInboundAdd(tx *gorm.DB, initIds string, inboundId uint, hostname string) error {
clientIds := strings.Split(initIds, ",")
var clients []model.Client
err := tx.Model(model.Client{}).Where("id in ?", clientIds).Find(&clients).Error
if err != nil {
return err
}
var inbound model.Inbound
err = tx.Model(model.Inbound{}).Preload("Tls").Where("id = ?", inboundId).Find(&inbound).Error
if err != nil {
return err
}
for _, client := range clients {
// Add inbounds
var clientInbounds []uint
json.Unmarshal(client.Inbounds, &clientInbounds)
clientInbounds = append(clientInbounds, inboundId)
client.Inbounds, err = json.MarshalIndent(clientInbounds, "", " ")
if err != nil {
return err
}
// Add links
var clientLinks, newClientLinks []map[string]string
json.Unmarshal(client.Links, &clientLinks)
newLinks := util.LinkGenerator(client.Config, &inbound, hostname)
for _, newLink := range newLinks {
newClientLinks = append(newClientLinks, map[string]string{
"remark": inbound.Tag,
"type": "local",
"uri": newLink,
})
}
for _, clientLink := range clientLinks {
if clientLink["remark"] != inbound.Tag {
newClientLinks = append(newClientLinks, clientLink)
}
}
client.Links, err = json.MarshalIndent(newClientLinks, "", " ")
if err != nil {
return err
}
err = tx.Save(&client).Error
if err != nil {
return err
}
}
return nil
}
func (s *ClientService) UpdateClientsOnInboundDelete(tx *gorm.DB, id uint, tag string) error {
var clients []model.Client
err := tx.Table("clients").
Where("EXISTS (SELECT 1 FROM json_each(clients.inbounds) WHERE json_each.value = ?)", id).
Find(&clients).Error
if err != nil {
return err
}
for _, client := range clients {
// Delete inbounds
var clientInbounds, newClientInbounds []uint
json.Unmarshal(client.Inbounds, &clientInbounds)
for _, clientInbound := range clientInbounds {
if clientInbound != id {
newClientInbounds = append(newClientInbounds, clientInbound)
}
}
client.Inbounds, err = json.MarshalIndent(newClientInbounds, "", " ")
if err != nil {
return err
}
// Delete links
var clientLinks, newClientLinks []map[string]string
json.Unmarshal(client.Links, &clientLinks)
for _, clientLink := range clientLinks {
if clientLink["remark"] != tag {
newClientLinks = append(newClientLinks, clientLink)
}
}
client.Links, err = json.MarshalIndent(newClientLinks, "", " ")
if err != nil {
return err
}
err = tx.Save(&client).Error
if err != nil {
return err
}
}
return nil
}
func (s *ClientService) UpdateLinksByInboundChange(tx *gorm.DB, inbounIds []uint, hostname string) error {
var inbounds []model.Inbound
err := tx.Model(model.Inbound{}).Preload("Tls").Where("id in ? and type in ?", inbounIds, util.InboundTypeWithLink).Find(&inbounds).Error
if err != nil && err != gorm.ErrRecordNotFound {
return err
}
for _, inbound := range inbounds {
var clients []model.Client
err = tx.Table("clients").
Where("EXISTS (SELECT 1 FROM json_each(clients.inbounds) WHERE json_each.value = ?)", inbound.Id).
Find(&clients).Error
if err != nil {
return err
}
for _, client := range clients {
var clientLinks, newClientLinks []map[string]string
json.Unmarshal(client.Links, &clientLinks)
newLinks := util.LinkGenerator(client.Config, &inbound, hostname)
for _, newLink := range newLinks {
newClientLinks = append(newClientLinks, map[string]string{
"remark": inbound.Tag,
"type": "local",
"uri": newLink,
})
}
for _, clientLink := range clientLinks {
if clientLink["remark"] != inbound.Tag {
newClientLinks = append(newClientLinks, clientLink)
}
}
client.Links, err = json.MarshalIndent(newClientLinks, "", " ")
if err != nil {
return err
}
err = tx.Save(&client).Error
if err != nil {
return err
}
}
}
return nil
}
func (s *ClientService) DepleteClients() error {
var err error
var clients []model.Client
var changes []model.Changes
var users []string
var inboundIds []uint
now := time.Now().Unix()
db := database.GetDB()
tx := db.Begin()
defer func() {
if err == nil {
tx.Commit()
if len(inboundIds) > 0 && corePtr.IsRunning() {
err1 := s.InboundService.RestartInbounds(tx, inboundIds)
if err1 != nil {
logger.Error("unable to restart inbounds: ", err1)
}
}
} else {
tx.Rollback()
}
}()
err = tx.Model(model.Client{}).Where("enable = true AND ((volume >0 AND up+down > volume) OR (expiry > 0 AND expiry < ?))", now).Scan(&clients).Error
if err != nil {
return err
}
dt := time.Now().Unix()
for _, client := range clients {
logger.Debug("Client ", client.Name, " is going to be disabled")
users = append(users, client.Name)
var userInbounds []uint
json.Unmarshal(client.Inbounds, &userInbounds)
inboundIds = s.uniqueAppendInboundIds(inboundIds, userInbounds)
changes = append(changes, model.Changes{
DateTime: dt,
Actor: "DepleteJob",
Key: "clients",
Action: "disable",
Obj: json.RawMessage("\"" + client.Name + "\""),
})
}
// Save changes
if len(changes) > 0 {
err = tx.Model(model.Client{}).Where("enable = true AND ((volume >0 AND up+down > volume) OR (expiry > 0 AND expiry < ?))", now).Update("enable", false).Error
if err != nil {
return err
}
err = tx.Model(model.Changes{}).Create(&changes).Error
if err != nil {
return err
}
LastUpdate = dt
}
return nil
}
// avoid duplicate inboundIds
func (s *ClientService) uniqueAppendInboundIds(a []uint, b []uint) []uint {
m := make(map[uint]bool)
for _, v := range a {
m[v] = true
}
for _, v := range b {
m[v] = true
}
var res []uint
for k := range m {
res = append(res, k)
}
return res
}