Files
frp-panel/services/dao/proxy.go
2025-05-06 02:08:59 +00:00

569 lines
17 KiB
Go

package dao
import (
"context"
"fmt"
"strings"
"time"
"github.com/VaalaCat/frp-panel/models"
"github.com/VaalaCat/frp-panel/pb"
"github.com/VaalaCat/frp-panel/utils"
"github.com/VaalaCat/frp-panel/utils/logger"
"github.com/samber/lo"
"github.com/sourcegraph/conc/pool"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
func (q *queryImpl) GetProxyStatsByClientID(userInfo models.UserInfo, clientID string) ([]*models.ProxyStatsEntity, error) {
if clientID == "" {
return nil, fmt.Errorf("invalid client id")
}
db := q.ctx.GetApp().GetDBManager().GetDefaultDB()
list := []*models.ProxyStats{}
err := db.
Where(&models.ProxyStats{ProxyStatsEntity: &models.ProxyStatsEntity{
UserID: userInfo.GetUserID(),
TenantID: userInfo.GetTenantID(),
ClientID: clientID,
}}).
Or(&models.ProxyStats{ProxyStatsEntity: &models.ProxyStatsEntity{
UserID: 0,
TenantID: userInfo.GetTenantID(),
ClientID: clientID,
}}).
Or(&models.ProxyStats{ProxyStatsEntity: &models.ProxyStatsEntity{
UserID: userInfo.GetUserID(),
TenantID: userInfo.GetTenantID(),
OriginClientID: clientID,
}}).
Or(&models.ProxyStats{ProxyStatsEntity: &models.ProxyStatsEntity{
UserID: 0,
TenantID: userInfo.GetTenantID(),
OriginClientID: clientID,
}}).
Find(&list).Error
if err != nil {
return nil, err
}
return lo.Map(list, func(item *models.ProxyStats, _ int) *models.ProxyStatsEntity {
return item.ProxyStatsEntity
}), nil
}
func (q *queryImpl) GetProxyStatsByServerID(userInfo models.UserInfo, serverID string) ([]*models.ProxyStatsEntity, error) {
if serverID == "" {
return nil, fmt.Errorf("invalid server id")
}
db := q.ctx.GetApp().GetDBManager().GetDefaultDB()
list := []*models.ProxyStats{}
err := db.
Where(&models.ProxyStats{ProxyStatsEntity: &models.ProxyStatsEntity{
UserID: userInfo.GetUserID(),
TenantID: userInfo.GetTenantID(),
ServerID: serverID,
}}).Or(&models.ProxyStats{ProxyStatsEntity: &models.ProxyStatsEntity{
UserID: 0,
TenantID: userInfo.GetTenantID(),
ServerID: serverID,
}}).
Find(&list).Error
if err != nil {
return nil, err
}
return lo.Map(list, func(item *models.ProxyStats, _ int) *models.ProxyStatsEntity {
return item.ProxyStatsEntity
}), nil
}
func (q *queryImpl) AdminUpdateProxyStats(srv *models.ServerEntity, inputs []*pb.ProxyInfo) error {
if srv.ServerID == "" {
return fmt.Errorf("invalid server id")
}
db := q.ctx.GetApp().GetDBManager().GetDefaultDB()
return db.Transaction(func(tx *gorm.DB) error {
queryResults := make([]interface{}, 3)
p := pool.New().WithErrors()
p.Go(
func() error {
user := models.User{}
if err := tx.Where(&models.User{
UserEntity: &models.UserEntity{
UserID: srv.UserID,
},
}).First(&user).Error; err != nil {
return err
}
queryResults[0] = user
return nil
},
)
p.Go(
func() error {
clients := []*models.Client{}
if err := tx.
Where(&models.Client{ClientEntity: &models.ClientEntity{
UserID: srv.UserID,
ServerID: srv.ServerID,
}}).Find(&clients).Error; err != nil {
return err
}
queryResults[1] = clients
return nil
},
)
p.Go(
func() error {
oldProxy := []*models.ProxyStats{}
if err := tx.
Where(&models.ProxyStats{ProxyStatsEntity: &models.ProxyStatsEntity{
UserID: srv.UserID,
ServerID: srv.ServerID,
}}).Find(&oldProxy).Error; err != nil {
return err
}
oldProxyMap := lo.SliceToMap(oldProxy, func(p *models.ProxyStats) (string, *models.ProxyStats) {
return p.Name, p
})
queryResults[2] = oldProxyMap
return nil
},
)
if err := p.Wait(); err != nil {
return err
}
user := queryResults[0].(models.User)
clients := queryResults[1].([]*models.Client)
oldProxyMap := queryResults[2].(map[string]*models.ProxyStats)
inputMap := map[string]*pb.ProxyInfo{}
proxyMap := map[string]*models.ProxyStatsEntity{}
for _, proxyInfo := range inputs {
if proxyInfo == nil {
continue
}
proxyName := strings.TrimPrefix(proxyInfo.GetName(), user.UserName+".")
proxyMap[proxyName] = &models.ProxyStatsEntity{
ServerID: srv.ServerID,
Name: proxyName,
Type: proxyInfo.GetType(),
UserID: srv.UserID,
TenantID: srv.TenantID,
TodayTrafficIn: proxyInfo.GetTodayTrafficIn(),
TodayTrafficOut: proxyInfo.GetTodayTrafficOut(),
}
inputMap[proxyName] = proxyInfo
}
proxyEntityMap := map[string]*models.ProxyStatsEntity{}
for _, client := range clients {
cliCfg, err := client.GetConfigContent()
if err != nil || cliCfg == nil {
continue
}
for _, cfg := range cliCfg.Proxies {
if proxy, ok := proxyMap[cfg.GetBaseConfig().Name]; ok {
proxy.ClientID = client.ClientID
proxy.OriginClientID = client.OriginClientID
proxyEntityMap[proxy.Name] = proxy
}
}
}
nowTime := time.Now()
results := lo.Values(lo.MapValues(proxyEntityMap, func(p *models.ProxyStatsEntity, name string) *models.ProxyStats {
item := &models.ProxyStats{
ProxyStatsEntity: p,
}
if oldProxy, ok := oldProxyMap[name]; ok {
item.ProxyID = oldProxy.ProxyID
firstSync := inputMap[name].GetFirstSync()
isSameDay := utils.IsSameDay(nowTime, oldProxy.UpdatedAt)
item.HistoryTrafficIn = oldProxy.HistoryTrafficIn
item.HistoryTrafficOut = oldProxy.HistoryTrafficOut
if !isSameDay || firstSync {
item.HistoryTrafficIn += oldProxy.TodayTrafficIn
item.HistoryTrafficOut += oldProxy.TodayTrafficOut
}
}
return item
}))
if len(results) > 0 {
return tx.Save(results).Error
}
return nil
})
}
func (q *queryImpl) AdminGetTenantProxyStats(tenantID int) ([]*models.ProxyStatsEntity, error) {
db := q.ctx.GetApp().GetDBManager().GetDefaultDB()
list := []*models.ProxyStats{}
err := db.
Where(&models.ProxyStats{ProxyStatsEntity: &models.ProxyStatsEntity{
TenantID: tenantID,
}}).
Find(&list).Error
if err != nil {
return nil, err
}
return lo.Map(list, func(item *models.ProxyStats, _ int) *models.ProxyStatsEntity {
return item.ProxyStatsEntity
}), nil
}
func (q *queryImpl) AdminGetAllProxyStats(tx *gorm.DB) ([]*models.ProxyStatsEntity, error) {
db := tx
list := []*models.ProxyStats{}
err := db.Clauses(clause.Locking{Strength: "UPDATE"}).
Find(&list).Error
if err != nil {
return nil, err
}
return lo.Map(list, func(item *models.ProxyStats, _ int) *models.ProxyStatsEntity {
return item.ProxyStatsEntity
}), nil
}
func (q *queryImpl) AdminCreateProxyConfig(proxyCfg *models.ProxyConfig) error {
db := q.ctx.GetApp().GetDBManager().GetDefaultDB()
return db.Create(proxyCfg).Error
}
// RebuildProxyConfigFromClient rebuild proxy from client
// skip stopped proxy
func (q *queryImpl) RebuildProxyConfigFromClient(userInfo models.UserInfo, client *models.Client) error {
db := q.ctx.GetApp().GetDBManager().GetDefaultDB()
pxyCfgs, err := utils.LoadProxiesFromContent(client.ConfigContent)
if err != nil {
return err
}
proxyConfigEntities := []*models.ProxyConfig{}
for _, pxyCfg := range pxyCfgs {
proxyCfg := &models.ProxyConfig{
ProxyConfigEntity: &models.ProxyConfigEntity{},
}
if oldProxyCfg, err := q.GetProxyConfigByOriginClientIDAndName(userInfo, client.ClientID, pxyCfg.GetBaseConfig().Name); err == nil {
logger.Logger(context.Background()).WithError(err).Warnf("proxy config already exist, will be override, clientID: [%s], name: [%s]",
client.ClientID, pxyCfg.GetBaseConfig().Name)
proxyCfg.Model = oldProxyCfg.Model
}
if err := proxyCfg.FillClientConfig(client.ClientEntity); err != nil {
return err
}
if err := proxyCfg.FillTypedProxyConfig(pxyCfg); err != nil {
return err
}
proxyConfigEntities = append(proxyConfigEntities, proxyCfg)
}
if err := q.DeleteProxyConfigsByClientIDOrOriginClientID(userInfo, client.ClientID); err != nil {
return err
}
if len(proxyConfigEntities) == 0 {
return nil
}
return db.Save(proxyConfigEntities).Error
}
func (q *queryImpl) AdminGetProxyConfigByClientIDAndName(clientID string, name string) (*models.ProxyConfig, error) {
db := q.ctx.GetApp().GetDBManager().GetDefaultDB()
proxyCfg := &models.ProxyConfig{}
err := db.
Where(&models.ProxyConfig{ProxyConfigEntity: &models.ProxyConfigEntity{
ClientID: clientID,
Name: name,
}}).
First(proxyCfg).Error
if err != nil {
return nil, err
}
return proxyCfg, nil
}
func (q *queryImpl) GetProxyConfigsByClientID(userInfo models.UserInfo, clientID string) ([]*models.ProxyConfigEntity, error) {
if clientID == "" {
return nil, fmt.Errorf("invalid client id")
}
db := q.ctx.GetApp().GetDBManager().GetDefaultDB()
list := []*models.ProxyConfig{}
err := db.
Where(&models.ProxyConfig{ProxyConfigEntity: &models.ProxyConfigEntity{
UserID: userInfo.GetUserID(),
TenantID: userInfo.GetTenantID(),
ClientID: clientID,
}}).
Find(&list).Error
if err != nil {
return nil, err
}
return lo.Map(list, func(item *models.ProxyConfig, _ int) *models.ProxyConfigEntity {
return item.ProxyConfigEntity
}), nil
}
func (q *queryImpl) GetProxyConfigByFilter(userInfo models.UserInfo, proxyConfig *models.ProxyConfigEntity) (*models.ProxyConfig, error) {
db := q.ctx.GetApp().GetDBManager().GetDefaultDB()
filter := &models.ProxyConfigEntity{}
if len(proxyConfig.ClientID) != 0 {
filter.ClientID = proxyConfig.ClientID
}
if len(proxyConfig.OriginClientID) != 0 {
filter.OriginClientID = proxyConfig.OriginClientID
}
if len(proxyConfig.Name) != 0 {
filter.Name = proxyConfig.Name
}
if len(proxyConfig.Type) != 0 {
filter.Type = proxyConfig.Type
}
if len(proxyConfig.ServerID) != 0 {
filter.ServerID = proxyConfig.ServerID
}
filter.UserID = userInfo.GetUserID()
filter.TenantID = userInfo.GetTenantID()
respProxyCfg := &models.ProxyConfig{}
err := db.
Where(&models.ProxyConfig{ProxyConfigEntity: filter}).
First(respProxyCfg).Error
if err != nil {
return nil, err
}
return respProxyCfg, nil
}
func (q *queryImpl) ListProxyConfigsWithFilters(userInfo models.UserInfo, page, pageSize int, filters *models.ProxyConfigEntity) ([]*models.ProxyConfig, error) {
if page < 1 || pageSize < 1 {
return nil, fmt.Errorf("invalid page or page size")
}
db := q.ctx.GetApp().GetDBManager().GetDefaultDB()
offset := (page - 1) * pageSize
filters.UserID = userInfo.GetUserID()
filters.TenantID = userInfo.GetTenantID()
var proxyConfigs []*models.ProxyConfig
err := db.Where(&models.ProxyConfig{
ProxyConfigEntity: filters,
}).Where(filters).Offset(offset).Limit(pageSize).Find(&proxyConfigs).Error
if err != nil {
return nil, err
}
return proxyConfigs, nil
}
func (q *queryImpl) AdminListProxyConfigsWithFilters(filters *models.ProxyConfigEntity) ([]*models.ProxyConfig, error) {
db := q.ctx.GetApp().GetDBManager().GetDefaultDB()
var proxyConfigs []*models.ProxyConfig
err := db.Where(&models.ProxyConfig{
ProxyConfigEntity: filters,
}).Where(filters).Find(&proxyConfigs).Error
if err != nil {
return nil, err
}
return proxyConfigs, nil
}
func (q *queryImpl) ListProxyConfigsWithFiltersAndKeyword(userInfo models.UserInfo, page, pageSize int, filters *models.ProxyConfigEntity, keyword string) ([]*models.ProxyConfig, error) {
if page < 1 || pageSize < 1 || len(keyword) == 0 {
return nil, fmt.Errorf("invalid page or page size or keyword")
}
db := q.ctx.GetApp().GetDBManager().GetDefaultDB()
offset := (page - 1) * pageSize
filters.UserID = userInfo.GetUserID()
filters.TenantID = userInfo.GetTenantID()
var proxyConfigs []*models.ProxyConfig
err := db.Where(&models.ProxyConfig{
ProxyConfigEntity: filters,
}).Where(filters).Where("name like ?", "%"+keyword+"%").Offset(offset).Limit(pageSize).Find(&proxyConfigs).Error
if err != nil {
return nil, err
}
return proxyConfigs, nil
}
func (q *queryImpl) ListProxyConfigsWithKeyword(userInfo models.UserInfo, page, pageSize int, keyword string) ([]*models.ProxyConfig, error) {
return q.ListProxyConfigsWithFiltersAndKeyword(userInfo, page, pageSize, &models.ProxyConfigEntity{}, keyword)
}
func (q *queryImpl) ListProxyConfigs(userInfo models.UserInfo, page, pageSize int) ([]*models.ProxyConfig, error) {
return q.ListProxyConfigsWithFilters(userInfo, page, pageSize, &models.ProxyConfigEntity{})
}
func (q *queryImpl) CreateProxyConfig(userInfo models.UserInfo, proxyCfg *models.ProxyConfigEntity) error {
db := q.ctx.GetApp().GetDBManager().GetDefaultDB()
proxyCfg.UserID = userInfo.GetUserID()
proxyCfg.TenantID = userInfo.GetTenantID()
return db.Create(&models.ProxyConfig{ProxyConfigEntity: proxyCfg}).Error
}
func (q *queryImpl) UpdateProxyConfig(userInfo models.UserInfo, proxyCfg *models.ProxyConfig) error {
if proxyCfg.ID == 0 {
return fmt.Errorf("invalid proxy config id")
}
db := q.ctx.GetApp().GetDBManager().GetDefaultDB()
proxyCfg.UserID = userInfo.GetUserID()
proxyCfg.TenantID = userInfo.GetTenantID()
return db.Where(&models.ProxyConfig{
ProxyConfigEntity: &models.ProxyConfigEntity{
UserID: userInfo.GetUserID(),
TenantID: userInfo.GetTenantID(),
ClientID: proxyCfg.ClientID,
},
Model: &gorm.Model{
ID: proxyCfg.ID,
},
}).Save(proxyCfg).Error
}
func (q *queryImpl) DeleteProxyConfig(userInfo models.UserInfo, clientID, name string) error {
if clientID == "" || name == "" {
return fmt.Errorf("invalid client id or name")
}
db := q.ctx.GetApp().GetDBManager().GetDefaultDB()
return db.Unscoped().
Where(&models.ProxyConfig{ProxyConfigEntity: &models.ProxyConfigEntity{
UserID: userInfo.GetUserID(),
TenantID: userInfo.GetTenantID(),
ClientID: clientID,
Name: name,
}}).
Delete(&models.ProxyConfig{}).Error
}
func (q *queryImpl) DeleteProxyConfigsByClientIDOrOriginClientID(userInfo models.UserInfo, clientID string) error {
if clientID == "" {
return fmt.Errorf("invalid client id")
}
db := q.ctx.GetApp().GetDBManager().GetDefaultDB()
return db.Unscoped().
Where(
db.Where(&models.ProxyConfig{ProxyConfigEntity: &models.ProxyConfigEntity{
UserID: userInfo.GetUserID(),
TenantID: userInfo.GetTenantID(),
ClientID: clientID,
}}).
Or(&models.ProxyConfig{ProxyConfigEntity: &models.ProxyConfigEntity{
UserID: userInfo.GetUserID(),
TenantID: userInfo.GetTenantID(),
OriginClientID: clientID,
}})).
Where(db.Where("stopped is NULL").
Or("stopped = ?", false)).
Delete(&models.ProxyConfig{}).Error
}
func (q *queryImpl) DeleteProxyConfigsByClientID(userInfo models.UserInfo, clientID string) error {
if clientID == "" {
return fmt.Errorf("invalid client id")
}
db := q.ctx.GetApp().GetDBManager().GetDefaultDB()
return db.Unscoped().
Where(&models.ProxyConfig{ProxyConfigEntity: &models.ProxyConfigEntity{
UserID: userInfo.GetUserID(),
TenantID: userInfo.GetTenantID(),
ClientID: clientID,
}}).
Delete(&models.ProxyConfig{}).Error
}
func (q *queryImpl) GetProxyConfigByOriginClientIDAndName(userInfo models.UserInfo, clientID string, name string) (*models.ProxyConfig, error) {
if clientID == "" || name == "" {
return nil, fmt.Errorf("invalid client id or name")
}
db := q.ctx.GetApp().GetDBManager().GetDefaultDB()
item := &models.ProxyConfig{}
err := db.
Where(&models.ProxyConfig{ProxyConfigEntity: &models.ProxyConfigEntity{
UserID: userInfo.GetUserID(),
TenantID: userInfo.GetTenantID(),
OriginClientID: clientID,
Name: name,
}}).
First(&item).Error
if err != nil {
return nil, err
}
return item, nil
}
func (q *queryImpl) CountProxyConfigs(userInfo models.UserInfo) (int64, error) {
return q.CountProxyConfigsWithFilters(userInfo, &models.ProxyConfigEntity{})
}
func (q *queryImpl) CountProxyConfigsWithFilters(userInfo models.UserInfo, filters *models.ProxyConfigEntity) (int64, error) {
db := q.ctx.GetApp().GetDBManager().GetDefaultDB()
filters.UserID = userInfo.GetUserID()
filters.TenantID = userInfo.GetTenantID()
var count int64
err := db.Model(&models.ProxyConfig{}).Where(&models.ProxyConfig{
ProxyConfigEntity: filters,
}).Count(&count).Error
if err != nil {
return 0, err
}
return count, nil
}
func (q *queryImpl) CountProxyConfigsWithFiltersAndKeyword(userInfo models.UserInfo, filters *models.ProxyConfigEntity, keyword string) (int64, error) {
if len(keyword) == 0 {
return q.CountProxyConfigsWithFilters(userInfo, filters)
}
db := q.ctx.GetApp().GetDBManager().GetDefaultDB()
filters.UserID = userInfo.GetUserID()
filters.TenantID = userInfo.GetTenantID()
var count int64
err := db.Model(&models.ProxyConfig{}).Where(&models.ProxyConfig{
ProxyConfigEntity: filters,
}).Where("name like ?", "%"+keyword+"%").Count(&count).Error
if err != nil {
return 0, err
}
return count, nil
}
func (q *queryImpl) GetProxyConfigsByWorkerId(userInfo models.UserInfo, workerID string) ([]*models.ProxyConfig, error) {
db := q.ctx.GetApp().GetDBManager().GetDefaultDB()
items := []*models.ProxyConfig{}
err := db.
Where(&models.ProxyConfig{ProxyConfigEntity: &models.ProxyConfigEntity{
UserID: userInfo.GetUserID(),
TenantID: userInfo.GetTenantID(),
},
WorkerID: workerID,
}).
Find(&items).Error
if err != nil {
return nil, err
}
return items, nil
}