Merge old/master into main, resolving conflicts by preferring old in workflows and removing deprecated adapter files
Build docker and publish / build (20.15.1) (push) Has been cancelled

This commit is contained in:
2025-12-28 15:56:07 -08:00
478 changed files with 13812 additions and 16686 deletions
@@ -0,0 +1,61 @@
package application
import (
"context"
"github.com/perfect-panel/server/internal/model/client"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/tool"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
)
type CreateSubscribeApplicationLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// NewCreateSubscribeApplicationLogic Create subscribe application
func NewCreateSubscribeApplicationLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateSubscribeApplicationLogic {
return &CreateSubscribeApplicationLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *CreateSubscribeApplicationLogic) CreateSubscribeApplication(req *types.CreateSubscribeApplicationRequest) (resp *types.SubscribeApplication, err error) {
var link client.DownloadLink
tool.DeepCopy(&link, req.DownloadLink)
linkData, err := link.Marshal()
if err != nil {
l.Errorf("Failed to marshal download link: %v", err)
return nil, errors.Wrap(xerr.NewErrCode(xerr.ERROR), " Failed to marshal download link")
}
data := &client.SubscribeApplication{
Name: req.Name,
Icon: req.Icon,
Description: req.Description,
Scheme: req.Scheme,
UserAgent: req.UserAgent,
IsDefault: req.IsDefault,
SubscribeTemplate: req.SubscribeTemplate,
OutputFormat: req.OutputFormat,
DownloadLink: string(linkData),
}
err = l.svcCtx.ClientModel.Insert(l.ctx, data)
if err != nil {
l.Errorf("Failed to create subscribe application: %v", err)
return nil, errors.Wrap(xerr.NewErrCode(xerr.DatabaseInsertError), "Failed to create subscribe application")
}
resp = &types.SubscribeApplication{}
tool.DeepCopy(resp, data)
resp.DownloadLink = req.DownloadLink
return
}
@@ -0,0 +1,35 @@
package application
import (
"context"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
)
type DeleteSubscribeApplicationLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// NewDeleteSubscribeApplicationLogic Delete subscribe application
func NewDeleteSubscribeApplicationLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeleteSubscribeApplicationLogic {
return &DeleteSubscribeApplicationLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *DeleteSubscribeApplicationLogic) DeleteSubscribeApplication(req *types.DeleteSubscribeApplicationRequest) error {
err := l.svcCtx.ClientModel.Delete(l.ctx, req.Id)
if err != nil {
l.Errorf("Failed to delete subscribe application with ID %d: %v", req.Id, err)
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseDeletedError), err.Error())
}
return nil
}
@@ -0,0 +1,61 @@
package application
import (
"context"
"encoding/json"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
)
type GetSubscribeApplicationListLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// NewGetSubscribeApplicationListLogic Get subscribe application list
func NewGetSubscribeApplicationListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetSubscribeApplicationListLogic {
return &GetSubscribeApplicationListLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetSubscribeApplicationListLogic) GetSubscribeApplicationList(req *types.GetSubscribeApplicationListRequest) (resp *types.GetSubscribeApplicationListResponse, err error) {
data, err := l.svcCtx.ClientModel.List(l.ctx)
if err != nil {
l.Errorf("Failed to get subscribe application list: %v", err)
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Failed to get subscribe application list")
}
var list []types.SubscribeApplication
for _, item := range data {
var temp types.DownloadLink
if item.DownloadLink != "" {
_ = json.Unmarshal([]byte(item.DownloadLink), &temp)
}
list = append(list, types.SubscribeApplication{
Id: item.Id,
Name: item.Name,
Description: item.Description,
Icon: item.Icon,
Scheme: item.Scheme,
UserAgent: item.UserAgent,
IsDefault: item.IsDefault,
SubscribeTemplate: item.SubscribeTemplate,
OutputFormat: item.OutputFormat,
DownloadLink: temp,
CreatedAt: item.CreatedAt.UnixMilli(),
UpdatedAt: item.UpdatedAt.UnixMilli(),
})
}
resp = &types.GetSubscribeApplicationListResponse{
Total: int64(len(list)),
List: list,
}
return
}
@@ -0,0 +1,74 @@
package application
import (
"context"
"time"
"github.com/perfect-panel/server/adapter"
"github.com/perfect-panel/server/internal/model/node"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
)
type PreviewSubscribeTemplateLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// Preview Template
func NewPreviewSubscribeTemplateLogic(ctx context.Context, svcCtx *svc.ServiceContext) *PreviewSubscribeTemplateLogic {
return &PreviewSubscribeTemplateLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *PreviewSubscribeTemplateLogic) PreviewSubscribeTemplate(req *types.PreviewSubscribeTemplateRequest) (resp *types.PreviewSubscribeTemplateResponse, err error) {
_, servers, err := l.svcCtx.NodeModel.FilterNodeList(l.ctx, &node.FilterNodeParams{
Page: 1,
Size: 1000,
Preload: true,
})
if err != nil {
l.Errorf("[PreviewSubscribeTemplateLogic] FindAllServer error: %v", err.Error())
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "FindAllServer error: %v", err.Error())
}
data, err := l.svcCtx.ClientModel.FindOne(l.ctx, req.Id)
if err != nil {
l.Errorf("[PreviewSubscribeTemplateLogic] FindOne error: %v", err.Error())
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "FindOneClient error: %v", err.Error())
}
sub := adapter.NewAdapter(data.SubscribeTemplate, adapter.WithServers(servers),
adapter.WithSiteName("PerfectPanel"),
adapter.WithSubscribeName("Test Subscribe"),
adapter.WithOutputFormat(data.OutputFormat),
adapter.WithUserInfo(adapter.User{
Password: "test-password",
ExpiredAt: time.Now().AddDate(1, 0, 0),
Download: 0,
Upload: 0,
Traffic: 1000,
SubscribeURL: "https://example.com/subscribe",
}))
// Get client config
a, err := sub.Client()
if err != nil {
l.Errorf("[PreviewSubscribeTemplateLogic] Client error: %v", err.Error())
return nil, errors.Wrapf(xerr.NewErrMsg(err.Error()), "Client error: %v", err.Error())
}
bytes, err := a.Build()
if err != nil {
l.Errorf("[PreviewSubscribeTemplateLogic] Build error: %v", err.Error())
return nil, errors.Wrapf(xerr.NewErrMsg(err.Error()), "Build error: %v", err.Error())
}
return &types.PreviewSubscribeTemplateResponse{
Template: string(bytes),
}, nil
}
@@ -0,0 +1,62 @@
package application
import (
"context"
"github.com/perfect-panel/server/internal/model/client"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/tool"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
)
type UpdateSubscribeApplicationLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// NewUpdateSubscribeApplicationLogic Update subscribe application
func NewUpdateSubscribeApplicationLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateSubscribeApplicationLogic {
return &UpdateSubscribeApplicationLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *UpdateSubscribeApplicationLogic) UpdateSubscribeApplication(req *types.UpdateSubscribeApplicationRequest) (resp *types.SubscribeApplication, err error) {
data, err := l.svcCtx.ClientModel.FindOne(l.ctx, req.Id)
if err != nil {
l.Errorf("Failed to find subscribe application with ID %d: %v", req.Id, err)
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Failed to find subscribe application with ID %d", req.Id)
}
var link client.DownloadLink
tool.DeepCopy(&link, req.DownloadLink)
linkData, err := link.Marshal()
if err != nil {
l.Errorf("Failed to marshal download link: %v", err)
return nil, errors.Wrap(xerr.NewErrCode(xerr.ERROR), " Failed to marshal download link")
}
data.Name = req.Name
data.Icon = req.Icon
data.Description = req.Description
data.Scheme = req.Scheme
data.UserAgent = req.UserAgent
data.IsDefault = req.IsDefault
data.SubscribeTemplate = req.SubscribeTemplate
data.OutputFormat = req.OutputFormat
data.DownloadLink = string(linkData)
err = l.svcCtx.ClientModel.Update(l.ctx, data)
if err != nil {
l.Errorf("Failed to update subscribe application with ID %d: %v", req.Id, err)
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "Failed to update subscribe application with ID %d", req.Id)
}
resp = &types.SubscribeApplication{}
tool.DeepCopy(&resp, data)
resp.DownloadLink = req.DownloadLink
return
}
@@ -50,8 +50,8 @@ func (l *QueryRevenueStatisticsLogic) QueryRevenueStatistics() (resp *types.Reve
// Get monthly's revenue statistics
monthlyData, err := l.svcCtx.OrderModel.QueryMonthlyOrders(l.ctx, now)
if err != nil {
l.Errorw("[QueryRevenueStatisticsLogic] QueryDateOrders error", logger.Field("error", err.Error()))
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "QueryDateOrders error: %v", err)
l.Errorw("[QueryRevenueStatisticsLogic] QueryMonthlyOrders error", logger.Field("error", err.Error()))
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "QueryMonthlyOrders error: %v", err)
} else {
monthly = types.OrdersStatistics{
AmountTotal: monthlyData.AmountTotal,
@@ -61,6 +61,24 @@ func (l *QueryRevenueStatisticsLogic) QueryRevenueStatistics() (resp *types.Reve
}
}
// Get monthly daily list for the current month (from 1st to current date)
monthlyListData, err := l.svcCtx.OrderModel.QueryDailyOrdersList(l.ctx, now)
if err != nil {
l.Errorw("[QueryRevenueStatisticsLogic] QueryDailyOrdersList error", logger.Field("error", err.Error()))
// Don't return error, just log it and continue with empty list
} else {
monthlyList := make([]types.OrdersStatistics, len(monthlyListData))
for i, data := range monthlyListData {
monthlyList[i] = types.OrdersStatistics{
Date: data.Date,
AmountTotal: data.AmountTotal,
NewOrderAmount: data.NewOrderAmount,
RenewalOrderAmount: data.RenewalOrderAmount,
}
}
monthly.List = monthlyList
}
// Get all revenue statistics
allData, err := l.svcCtx.OrderModel.QueryTotalOrders(l.ctx)
if err != nil {
@@ -74,6 +92,25 @@ func (l *QueryRevenueStatisticsLogic) QueryRevenueStatistics() (resp *types.Reve
List: make([]types.OrdersStatistics, 0),
}
}
// Get all monthly list for the past 6 months
allListData, err := l.svcCtx.OrderModel.QueryMonthlyOrdersList(l.ctx, now)
if err != nil {
l.Errorw("[QueryRevenueStatisticsLogic] QueryMonthlyOrdersList error", logger.Field("error", err.Error()))
// Don't return error, just log it and continue with empty list
} else {
allList := make([]types.OrdersStatistics, len(allListData))
for i, data := range allListData {
allList[i] = types.OrdersStatistics{
Date: data.Date,
AmountTotal: data.AmountTotal,
NewOrderAmount: data.NewOrderAmount,
RenewalOrderAmount: data.RenewalOrderAmount,
}
}
all.List = allList
}
return &types.RevenueStatisticsResponse{
Today: today,
Monthly: monthly,
@@ -85,7 +122,7 @@ func (l *QueryRevenueStatisticsLogic) QueryRevenueStatistics() (resp *types.Reve
func (l *QueryRevenueStatisticsLogic) mockRevenueStatistics() *types.RevenueStatisticsResponse {
now := time.Now()
// Generate daily data for the past 7 days (oldest first)
// Generate daily data for the current month (from 1st to current date)
monthlyList := make([]types.OrdersStatistics, 7)
for i := 0; i < 7; i++ {
dayDate := now.AddDate(0, 0, -(6 - i))
@@ -6,12 +6,15 @@ import (
"strings"
"time"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/perfect-panel/server/internal/model/log"
"github.com/perfect-panel/server/internal/model/node"
"github.com/perfect-panel/server/internal/model/traffic"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
"gorm.io/gorm"
)
type QueryServerTotalDataLogic struct {
@@ -35,127 +38,194 @@ func (l *QueryServerTotalDataLogic) QueryServerTotalData() (resp *types.ServerTo
return l.mockRevenueStatistics(), nil
}
resp = &types.ServerTotalDataResponse{
ServerTrafficRankingToday: make([]types.ServerTrafficData, 0),
ServerTrafficRankingYesterday: make([]types.ServerTrafficData, 0),
UserTrafficRankingToday: make([]types.UserTrafficData, 0),
UserTrafficRankingYesterday: make([]types.UserTrafficData, 0),
now := time.Now()
todayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
todayEnd := todayStart.Add(24 * time.Hour).Add(-time.Second)
query := l.svcCtx.DB.WithContext(l.ctx)
var todayTop10User []log.UserTraffic
err = query.Model(&traffic.TrafficLog{}).
Select("user_id, subscribe_id, SUM(download + upload) AS total, SUM(download) AS download, SUM(upload) AS upload").
Where("timestamp BETWEEN ? AND ?", todayStart, todayEnd).
Group("user_id, subscribe_id").
Order("total DESC").
Limit(10).
Scan(&todayTop10User).Error
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
logger.Errorf("[Traffic Stat Queue] Query user traffic failed: %v", err.Error())
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), " Query user traffic failed: %v", err.Error())
}
var userTodayTrafficRanking []types.UserTrafficData
for _, item := range todayTop10User {
userTodayTrafficRanking = append(userTodayTrafficRanking, types.UserTrafficData{
SID: item.SubscribeId,
Upload: item.Upload,
Download: item.Download,
})
}
// Query node server status
servers, err := l.svcCtx.ServerModel.FindAllServer(l.ctx)
if err != nil {
l.Errorw("[QueryServerTotalDataLogic] FindAllServer error", logger.Field("error", err.Error()))
return nil, errors.Wrapf(err, "FindAllServer error: %v", err)
}
onlineServers, err := l.svcCtx.NodeCache.GetOnlineNodeStatusCount(l.ctx)
if err != nil {
l.Errorw("[QueryServerTotalDataLogic] GetOnlineNodeStatusCount error", logger.Field("error", err.Error()))
return nil, errors.Wrapf(err, "GetOnlineNodeStatusCount error: %v", err)
}
resp.OnlineServers = onlineServers
resp.OfflineServers = int64(len(servers) - int(onlineServers))
// query yesterday user traffic rank log
yesterday := todayStart.Add(-24 * time.Hour).Format(time.DateOnly)
// 获取所有节点在线用户
allNodeOnlineUser, err := l.svcCtx.NodeCache.GetAllNodeOnlineUser(l.ctx)
if err != nil {
l.Errorw("[QueryServerTotalDataLogic] Get all node online user failed", logger.Field("error", err.Error()))
var yesterdayLog log.SystemLog
err = query.Model(&log.SystemLog{}).Where("`date` = ? AND `type` = ?", yesterday, log.TypeUserTrafficRank).First(&yesterdayLog).Error
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
l.Errorw("[QueryServerTotalDataLogic] Query yesterday user traffic rank log error", logger.Field("error", err.Error()))
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Query yesterday user traffic rank log error: %v", err)
}
resp.OnlineUserIPs = int64(len(allNodeOnlineUser))
// 获取所有节点今日上传下载流量
allNodeUploadTraffic, err := l.svcCtx.NodeCache.GetAllNodeUploadTraffic(l.ctx)
if err != nil {
l.Errorw("[QueryServerTotalDataLogic] Get all node upload traffic failed", logger.Field("error", err.Error()))
var yesterdayUserRankData []types.UserTrafficData
if yesterdayLog.Id > 0 {
var rank log.UserTrafficRank
err = rank.Unmarshal([]byte(yesterdayLog.Content))
if err != nil {
l.Errorw("[QueryServerTotalDataLogic] Unmarshal yesterday user traffic rank log error", logger.Field("error", err.Error()))
}
for _, v := range rank.Rank {
yesterdayUserRankData = append(yesterdayUserRankData, types.UserTrafficData{
SID: v.SubscribeId,
Upload: v.Upload,
Download: v.Download,
})
}
}
resp.TodayUpload = allNodeUploadTraffic
allNodeDownloadTraffic, err := l.svcCtx.NodeCache.GetAllNodeDownloadTraffic(l.ctx)
if err != nil {
l.Errorw("[QueryServerTotalDataLogic] Get all node download traffic failed", logger.Field("error", err.Error()))
// query server traffic rank today
var todayTop10Server []log.ServerTraffic
err = query.Model(&traffic.TrafficLog{}).Select("server_id, SUM(download + upload) AS total, SUM(download) AS download, SUM(upload) AS upload").
Where("timestamp BETWEEN ? AND ?", todayStart, todayEnd).
Group("server_id").
Order("total DESC").
Limit(10).
Scan(&todayTop10Server).Error
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
logger.Errorf("[Traffic Stat Queue] Query server traffic failed: %v", err.Error())
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), " Query server traffic failed: %v", err.Error())
}
resp.TodayDownload = allNodeDownloadTraffic
// 获取节点流量排行榜 前10
nodeTrafficRankingToday, err := l.svcCtx.NodeCache.GetNodeTodayTotalTrafficRank(l.ctx, 10)
if err != nil {
l.Errorw("[QueryServerTotalDataLogic] Get node today total traffic rank failed", logger.Field("error", err.Error()))
var todayServerRanking []types.ServerTrafficData
for _, item := range todayTop10Server {
info, err := l.svcCtx.NodeModel.FindOneServer(l.ctx, item.ServerId)
if err != nil {
l.Errorw("[QueryServerTotalDataLogic] FindOneServer error", logger.Field("error", err.Error()), logger.Field("server_id", item.ServerId))
continue
}
todayServerRanking = append(todayServerRanking, types.ServerTrafficData{
ServerId: item.ServerId,
Name: info.Name,
Upload: item.Upload,
Download: item.Download,
})
}
if len(nodeTrafficRankingToday) > 0 {
var serverTrafficData []types.ServerTrafficData
for _, rank := range nodeTrafficRankingToday {
serverInfo, err := l.svcCtx.ServerModel.FindOne(l.ctx, rank.ID)
// query server traffic rank yesterday
var yesterdayTop10Server []types.ServerTrafficData
var yesterdayServerTrafficLog log.SystemLog
err = query.Model(&log.SystemLog{}).Where("`date` = ? AND `type` = ?", yesterday, log.TypeServerTrafficRank).First(&yesterdayServerTrafficLog).Error
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
l.Errorw("[QueryServerTotalDataLogic] Query yesterday server traffic rank log error", logger.Field("error", err.Error()))
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Query yesterday server traffic rank log error: %v", err)
}
if yesterdayServerTrafficLog.Id > 0 {
var rank log.ServerTrafficRank
err = rank.Unmarshal([]byte(yesterdayServerTrafficLog.Content))
if err != nil {
l.Errorw("[QueryServerTotalDataLogic] Unmarshal yesterday server traffic rank log error", logger.Field("error", err.Error()))
}
for _, v := range rank.Rank {
info, err := l.svcCtx.NodeModel.FindOneServer(l.ctx, v.ServerId)
if err != nil {
l.Errorw("[QueryServerTotalDataLogic] FindOne error", logger.Field("error", err))
l.Errorw("[QueryServerTotalDataLogic] FindOneServer error", logger.Field("error", err.Error()), logger.Field("server_id", v.ServerId))
continue
}
serverTrafficData = append(serverTrafficData, types.ServerTrafficData{
ServerId: rank.ID,
Name: serverInfo.Name,
Upload: rank.Upload,
Download: rank.Download,
yesterdayTop10Server = append(yesterdayTop10Server, types.ServerTrafficData{
ServerId: v.ServerId,
Name: info.Name,
Upload: v.Upload,
Download: v.Download,
})
}
resp.ServerTrafficRankingToday = serverTrafficData
}
// 获取用户流量排行榜 前10
userTrafficRankingToday, err := l.svcCtx.NodeCache.GetUserTodayTotalTrafficRank(l.ctx, 10)
if err != nil {
l.Errorw("[QueryServerTotalDataLogic] Get user today total traffic rank failed", logger.Field("error", err.Error()))
}
if len(userTrafficRankingToday) > 0 {
var userTrafficData []types.UserTrafficData
for _, rank := range userTrafficRankingToday {
userTrafficData = append(userTrafficData, types.UserTrafficData{
SID: rank.SID,
Upload: rank.Upload,
Download: rank.Download,
})
}
resp.UserTrafficRankingToday = userTrafficData
}
// 获取昨日节点流量排行榜 前10
nodeTrafficRankingYesterday, err := l.svcCtx.NodeCache.GetYesterdayNodeTotalTrafficRank(l.ctx)
// query online user count
onlineUsers, err := l.svcCtx.NodeModel.OnlineUserSubscribeGlobal(l.ctx)
if err != nil {
l.Errorw("[QueryServerTotalDataLogic] Get yesterday node total traffic rank failed", logger.Field("error", err.Error()))
}
if len(nodeTrafficRankingYesterday) > 0 {
var serverTrafficData []types.ServerTrafficData
for _, rank := range nodeTrafficRankingYesterday {
serverTrafficData = append(serverTrafficData, types.ServerTrafficData{
ServerId: rank.ID,
Name: rank.Name,
Upload: rank.Upload,
Download: rank.Download,
})
}
resp.ServerTrafficRankingYesterday = serverTrafficData
}
// 获取昨日用户流量排行榜 前10
userTrafficRankingYesterday, err := l.svcCtx.NodeCache.GetYesterdayUserTotalTrafficRank(l.ctx)
if err != nil {
l.Errorw("[QueryServerTotalDataLogic] Get yesterday user total traffic rank failed", logger.Field("error", err.Error()))
}
if len(userTrafficRankingYesterday) > 0 {
var userTrafficData []types.UserTrafficData
for _, rank := range userTrafficRankingYesterday {
userTrafficData = append(userTrafficData, types.UserTrafficData{
SID: rank.SID,
Upload: rank.Upload,
Download: rank.Download,
})
}
resp.UserTrafficRankingYesterday = userTrafficData
l.Errorw("[QueryServerTotalDataLogic] OnlineUserSubscribeGlobal error", logger.Field("error", err.Error()))
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "OnlineUserSubscribeGlobal error: %v", err)
}
// Query node traffic by monthly
nodeTraffic, err := l.svcCtx.TrafficLogModel.QueryTrafficByMonthly(l.ctx, time.Now())
if err != nil {
l.Errorw("[QueryServerTotalDataLogic] QueryTrafficByMonthly error", logger.Field("error", err.Error()))
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "QueryTrafficByMonthly error: %v", err.Error())
// query online/offline server count
var onlineServers, offlineServers int64
err = query.Model(&node.Server{}).Where("`last_reported_at` > ?", now.Add(-5*time.Minute)).Count(&onlineServers).Error
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
l.Errorw("[QueryServerTotalDataLogic] Count online servers error", logger.Field("error", err.Error()))
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Count online servers error: %v", err)
}
err = query.Model(&node.Server{}).Where("`last_reported_at` <= ? OR `last_reported_at` IS NULL", now.Add(-5*time.Minute)).Count(&offlineServers).Error
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
l.Errorw("[QueryServerTotalDataLogic] Count offline servers error", logger.Field("error", err.Error()))
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Count offline servers error: %v", err)
}
// TodayUpload, TodayDownload, MonthlyUpload, MonthlyDownload
var todayUpload, todayDownload, monthlyUpload, monthlyDownload int64
type trafficSum struct {
Upload int64
Download int64
}
var todayTraffic trafficSum
// Today
err = query.Model(&traffic.TrafficLog{}).Select("SUM(upload) AS upload, SUM(download) AS download").
Where("timestamp BETWEEN ? AND ?", todayStart, todayEnd).
Scan(&todayTraffic).Error
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
l.Errorw("[QueryServerTotalDataLogic] Sum today traffic error", logger.Field("error", err.Error()))
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Sum today traffic error: %v", err)
}
todayUpload = todayTraffic.Upload
todayDownload = todayTraffic.Download
// Monthly
monthlyUpload += todayUpload
monthlyDownload += todayDownload
for i := now.Day() - 1; i >= 1; i-- {
var logInfo log.SystemLog
date := time.Date(now.Year(), now.Month(), i, 0, 0, 0, 0, now.Location()).Format(time.DateOnly)
err = query.Model(&log.SystemLog{}).Where("`date` = ? AND `type` = ?", date, log.TypeTrafficStat).First(&logInfo).Error
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
l.Errorw("[QueryServerTotalDataLogic] Query daily traffic stat log error", logger.Field("error", err.Error()), logger.Field("date", date))
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Query daily traffic stat log error: %v", err)
}
if logInfo.Id > 0 {
var stat log.TrafficStat
err = stat.Unmarshal([]byte(logInfo.Content))
if err != nil {
l.Errorw("[QueryServerTotalDataLogic] Unmarshal daily traffic stat log error", logger.Field("error", err.Error()), logger.Field("date", date))
continue
}
monthlyUpload += stat.Upload
monthlyDownload += stat.Download
}
}
resp = &types.ServerTotalDataResponse{
OnlineUsers: onlineUsers,
OnlineServers: onlineServers,
OfflineServers: offlineServers,
TodayUpload: todayUpload,
TodayDownload: todayDownload,
MonthlyUpload: monthlyUpload,
MonthlyDownload: monthlyDownload,
UpdatedAt: now.Unix(),
ServerTrafficRankingToday: todayServerRanking,
ServerTrafficRankingYesterday: yesterdayTop10Server,
UserTrafficRankingToday: userTodayTrafficRanking,
UserTrafficRankingYesterday: yesterdayUserRankData,
}
resp.MonthlyUpload = nodeTraffic.Upload
resp.MonthlyDownload = nodeTraffic.Download
return resp, nil
}
@@ -215,7 +285,7 @@ func (l *QueryServerTotalDataLogic) mockRevenueStatistics() *types.ServerTotalDa
//}
//
return &types.ServerTotalDataResponse{
OnlineUserIPs: 1688,
OnlineUsers: 1688,
OnlineServers: 8,
OfflineServers: 2,
TodayUpload: 8888888888, // ~8.3GB
@@ -61,8 +61,24 @@ func (l *QueryUserStatisticsLogic) QueryUserStatistics() (resp *types.UserStatis
} else {
resp.Monthly.NewOrderUsers = newMonth
resp.Monthly.RenewalOrderUsers = renewalMonth
// TODO: Check the purchase status in the past seven days
resp.Monthly.List = make([]types.UserStatistics, 0)
}
// Get monthly daily user statistics list for the current month (from 1st to current date)
monthlyListData, err := l.svcCtx.UserModel.QueryDailyUserStatisticsList(l.ctx, now)
if err != nil {
l.Errorw("[QueryUserStatisticsLogic] QueryDailyUserStatisticsList error", logger.Field("error", err.Error()))
// Don't return error, just log it and continue with empty list
} else {
monthlyList := make([]types.UserStatistics, len(monthlyListData))
for i, data := range monthlyListData {
monthlyList[i] = types.UserStatistics{
Date: data.Date,
Register: data.Register,
NewOrderUsers: data.NewOrderUsers,
RenewalOrderUsers: data.RenewalOrderUsers,
}
}
resp.Monthly.List = monthlyList
}
// query all user count
@@ -81,13 +97,32 @@ func (l *QueryUserStatisticsLogic) QueryUserStatistics() (resp *types.UserStatis
resp.All.NewOrderUsers = allNewOrderUsers
resp.All.RenewalOrderUsers = allRenewalOrderUsers
}
// Get all monthly user statistics list for the past 6 months
allListData, err := l.svcCtx.UserModel.QueryMonthlyUserStatisticsList(l.ctx, now)
if err != nil {
l.Errorw("[QueryUserStatisticsLogic] QueryMonthlyUserStatisticsList error", logger.Field("error", err.Error()))
// Don't return error, just log it and continue with empty list
} else {
allList := make([]types.UserStatistics, len(allListData))
for i, data := range allListData {
allList[i] = types.UserStatistics{
Date: data.Date,
Register: data.Register,
NewOrderUsers: data.NewOrderUsers,
RenewalOrderUsers: data.RenewalOrderUsers,
}
}
resp.All.List = allList
}
return
}
func (l *QueryUserStatisticsLogic) mockRevenueStatistics() *types.UserStatisticsResponse {
now := time.Now()
// Generate daily user statistics for the past 7 days (oldest first)
// Generate daily user statistics for the current month (from 1st to current date)
monthlyList := make([]types.UserStatistics, 7)
for i := 0; i < 7; i++ {
dayDate := now.AddDate(0, 0, -(6 - i))
@@ -100,6 +135,19 @@ func (l *QueryUserStatisticsLogic) mockRevenueStatistics() *types.UserStatistics
}
}
// Generate monthly user statistics for the past 6 months (oldest first)
allList := make([]types.UserStatistics, 6)
for i := 0; i < 6; i++ {
monthDate := now.AddDate(0, -(5 - i), 0)
baseRegister := int64(1800 + ((5 - i) * 200) + ((5-i)%2)*500)
allList[i] = types.UserStatistics{
Date: monthDate.Format("2006-01"),
Register: baseRegister,
NewOrderUsers: int64(float64(baseRegister) * 0.65),
RenewalOrderUsers: int64(float64(baseRegister) * 0.35),
}
}
return &types.UserStatisticsResponse{
Today: types.UserStatistics{
Register: 28,
@@ -116,6 +164,7 @@ func (l *QueryUserStatisticsLogic) mockRevenueStatistics() *types.UserStatistics
Register: 18888,
NewOrderUsers: 0, // This field is not used in All statistics
RenewalOrderUsers: 0, // This field is not used in All statistics
List: allList,
},
}
}
@@ -0,0 +1,64 @@
package log
import (
"context"
"github.com/perfect-panel/server/internal/model/log"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
)
type FilterBalanceLogLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// NewFilterBalanceLogLogic Filter balance log
func NewFilterBalanceLogLogic(ctx context.Context, svcCtx *svc.ServiceContext) *FilterBalanceLogLogic {
return &FilterBalanceLogLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *FilterBalanceLogLogic) FilterBalanceLog(req *types.FilterBalanceLogRequest) (resp *types.FilterBalanceLogResponse, err error) {
data, total, err := l.svcCtx.LogModel.FilterSystemLog(l.ctx, &log.FilterParams{
Page: req.Page,
Size: req.Size,
Type: log.TypeBalance.Uint8(),
Data: req.Date,
ObjectID: req.UserId,
})
if err != nil {
l.Errorw("[FilterBalanceLog] Query User Balance Log Error:", logger.Field("error", err.Error()))
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Query User Balance Log Error")
}
list := make([]types.BalanceLog, 0)
for _, datum := range data {
var content log.Balance
if err = content.Unmarshal([]byte(datum.Content)); err != nil {
l.Errorf("[QueryUserBalanceLog] unmarshal balance log content failed: %v", err.Error())
continue
}
list = append(list, types.BalanceLog{
UserId: datum.ObjectID,
Amount: content.Amount,
Type: content.Type,
OrderNo: content.OrderNo,
Balance: content.Balance,
Timestamp: content.Timestamp,
})
}
return &types.FilterBalanceLogResponse{
Total: total,
List: list,
}, nil
}
@@ -0,0 +1,61 @@
package log
import (
"context"
"github.com/perfect-panel/server/internal/model/log"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
)
type FilterCommissionLogLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// NewFilterCommissionLogLogic Filter commission log
func NewFilterCommissionLogLogic(ctx context.Context, svcCtx *svc.ServiceContext) *FilterCommissionLogLogic {
return &FilterCommissionLogLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *FilterCommissionLogLogic) FilterCommissionLog(req *types.FilterCommissionLogRequest) (resp *types.FilterCommissionLogResponse, err error) {
data, total, err := l.svcCtx.LogModel.FilterSystemLog(l.ctx, &log.FilterParams{
Page: req.Page,
Size: req.Size,
Data: req.Date,
Type: log.TypeCommission.Uint8(),
ObjectID: req.UserId,
})
if err != nil {
l.Errorw("Query User Commission Log failed", logger.Field("error", err.Error()))
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Query User Commission Log failed")
}
var list []types.CommissionLog
for _, datum := range data {
var content log.Commission
if err = content.Unmarshal([]byte(datum.Content)); err != nil {
l.Errorf("unmarshal commission log content failed: %v", err.Error())
continue
}
list = append(list, types.CommissionLog{
UserId: datum.ObjectID,
Type: content.Type,
Amount: content.Amount,
OrderNo: content.OrderNo,
Timestamp: content.Timestamp,
})
}
return &types.FilterCommissionLogResponse{
Total: total,
List: list,
}, nil
}
@@ -0,0 +1,68 @@
package log
import (
"context"
"github.com/perfect-panel/server/internal/model/log"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
)
type FilterEmailLogLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// NewFilterEmailLogLogic Filter email log
func NewFilterEmailLogLogic(ctx context.Context, svcCtx *svc.ServiceContext) *FilterEmailLogLogic {
return &FilterEmailLogLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *FilterEmailLogLogic) FilterEmailLog(req *types.FilterLogParams) (resp *types.FilterEmailLogResponse, err error) {
data, total, err := l.svcCtx.LogModel.FilterSystemLog(l.ctx, &log.FilterParams{
Page: req.Page,
Size: req.Size,
Type: log.TypeEmailMessage.Uint8(),
Data: req.Date,
Search: req.Search,
})
if err != nil {
l.Errorf("[FilterEmailLog] failed to filter system log: %v", err.Error())
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "failed to filter system log: %v", err.Error())
}
var list []types.MessageLog
for _, datum := range data {
var content log.Message
err = content.Unmarshal([]byte(datum.Content))
if err != nil {
l.Errorf("[FilterEmailLog] failed to unmarshal content: %v", err.Error())
continue
}
list = append(list, types.MessageLog{
Id: datum.Id,
Type: datum.Type,
Platform: content.Platform,
To: content.To,
Subject: content.Subject,
Content: content.Content,
Status: content.Status,
CreatedAt: datum.CreatedAt.UnixMilli(),
})
}
return &types.FilterEmailLogResponse{
Total: total,
List: list,
}, nil
}
@@ -0,0 +1,68 @@
package log
import (
"context"
"github.com/perfect-panel/server/internal/model/log"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
)
type FilterGiftLogLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// Filter gift log
func NewFilterGiftLogLogic(ctx context.Context, svcCtx *svc.ServiceContext) *FilterGiftLogLogic {
return &FilterGiftLogLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *FilterGiftLogLogic) FilterGiftLog(req *types.FilterGiftLogRequest) (resp *types.FilterGiftLogResponse, err error) {
data, total, err := l.svcCtx.LogModel.FilterSystemLog(l.ctx, &log.FilterParams{
Page: req.Page,
Size: req.Size,
Type: log.TypeGift.Uint8(),
ObjectID: req.UserId,
Data: req.Date,
Search: req.Search,
})
if err != nil {
l.Errorf("[FilterGiftLog] failed to filter system log: %v", err.Error())
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "failed to filter system log: %v", err.Error())
}
var list []types.GiftLog
for _, datum := range data {
var content log.Gift
err = content.Unmarshal([]byte(datum.Content))
if err != nil {
l.Errorf("[FilterGiftLog] failed to unmarshal content: %v", err.Error())
continue
}
list = append(list, types.GiftLog{
Type: content.Type,
UserId: datum.ObjectID,
OrderNo: content.OrderNo,
SubscribeId: content.SubscribeId,
Amount: content.Amount,
Balance: content.Balance,
Remark: content.Remark,
Timestamp: content.Timestamp,
})
}
return &types.FilterGiftLogResponse{
Total: total,
List: list,
}, nil
}
@@ -0,0 +1,65 @@
package log
import (
"context"
"github.com/perfect-panel/server/internal/model/log"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
)
type FilterLoginLogLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// NewFilterLoginLogLogic Filter login log
func NewFilterLoginLogLogic(ctx context.Context, svcCtx *svc.ServiceContext) *FilterLoginLogLogic {
return &FilterLoginLogLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *FilterLoginLogLogic) FilterLoginLog(req *types.FilterLoginLogRequest) (resp *types.FilterLoginLogResponse, err error) {
data, total, err := l.svcCtx.LogModel.FilterSystemLog(l.ctx, &log.FilterParams{
Page: req.Page,
Size: req.Size,
Type: log.TypeLogin.Uint8(),
ObjectID: req.UserId,
Data: req.Date,
Search: req.Search,
})
if err != nil {
l.Errorf("[FilterLoginLog] failed to filter system log: %v", err.Error())
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "failed to filter system log: %v", err.Error())
}
var list []types.LoginLog
for _, datum := range data {
var item log.Login
err = item.Unmarshal([]byte(datum.Content))
if err != nil {
l.Errorf("[FilterLoginLog] failed to unmarshal content: %v", err.Error())
continue
}
list = append(list, types.LoginLog{
UserId: datum.ObjectID,
Method: item.Method,
LoginIP: item.LoginIP,
UserAgent: item.UserAgent,
Success: item.Success,
Timestamp: datum.CreatedAt.UnixMilli(),
})
}
return &types.FilterLoginLogResponse{
Total: total,
List: list,
}, nil
}
@@ -0,0 +1,68 @@
package log
import (
"context"
"github.com/perfect-panel/server/internal/model/log"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
)
type FilterMobileLogLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// Filter mobile log
func NewFilterMobileLogLogic(ctx context.Context, svcCtx *svc.ServiceContext) *FilterMobileLogLogic {
return &FilterMobileLogLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *FilterMobileLogLogic) FilterMobileLog(req *types.FilterLogParams) (resp *types.FilterMobileLogResponse, err error) {
data, total, err := l.svcCtx.LogModel.FilterSystemLog(l.ctx, &log.FilterParams{
Page: req.Page,
Size: req.Size,
Type: log.TypeMobileMessage.Uint8(),
Data: req.Date,
Search: req.Search,
})
if err != nil {
l.Errorf("[FilterMobileLog] failed to filter system log: %v", err.Error())
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "failed to filter system log: %v", err.Error())
}
var list []types.MessageLog
for _, datum := range data {
var content log.Message
err = content.Unmarshal([]byte(datum.Content))
if err != nil {
l.Errorf("[FilterMobileLog] failed to unmarshal content: %v", err.Error())
continue
}
list = append(list, types.MessageLog{
Id: datum.Id,
Type: datum.Type,
Platform: content.Platform,
To: content.To,
Subject: content.Subject,
Content: content.Content,
Status: content.Status,
CreatedAt: datum.CreatedAt.UnixMilli(),
})
}
return &types.FilterMobileLogResponse{
Total: total,
List: list,
}, nil
}
@@ -0,0 +1,66 @@
package log
import (
"context"
"github.com/perfect-panel/server/internal/model/log"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
)
type FilterRegisterLogLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// Filter register log
func NewFilterRegisterLogLogic(ctx context.Context, svcCtx *svc.ServiceContext) *FilterRegisterLogLogic {
return &FilterRegisterLogLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *FilterRegisterLogLogic) FilterRegisterLog(req *types.FilterRegisterLogRequest) (resp *types.FilterRegisterLogResponse, err error) {
data, total, err := l.svcCtx.LogModel.FilterSystemLog(l.ctx, &log.FilterParams{
Page: req.Page,
Size: req.Size,
Type: log.TypeRegister.Uint8(),
ObjectID: req.UserId,
Data: req.Date,
Search: req.Search,
})
if err != nil {
l.Errorf("[FilterRegisterLog] failed to filter system log: %v", err.Error())
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "failed to filter system log: %v", err.Error())
}
var list []types.RegisterLog
for _, datum := range data {
var item log.Register
err = item.Unmarshal([]byte(datum.Content))
if err != nil {
l.Errorf("[FilterLoginLog] failed to unmarshal content: %v", err.Error())
continue
}
list = append(list, types.RegisterLog{
UserId: datum.ObjectID,
AuthMethod: item.AuthMethod,
Identifier: item.Identifier,
RegisterIP: item.RegisterIP,
UserAgent: item.UserAgent,
Timestamp: item.Timestamp,
})
}
return &types.FilterRegisterLogResponse{
List: list,
Total: total,
}, nil
}
@@ -0,0 +1,66 @@
package log
import (
"context"
"github.com/perfect-panel/server/internal/model/log"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
)
type FilterResetSubscribeLogLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// NewFilterResetSubscribeLogLogic Filter reset subscribe log
func NewFilterResetSubscribeLogLogic(ctx context.Context, svcCtx *svc.ServiceContext) *FilterResetSubscribeLogLogic {
return &FilterResetSubscribeLogLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *FilterResetSubscribeLogLogic) FilterResetSubscribeLog(req *types.FilterResetSubscribeLogRequest) (resp *types.FilterResetSubscribeLogResponse, err error) {
data, total, err := l.svcCtx.LogModel.FilterSystemLog(l.ctx, &log.FilterParams{
Page: req.Page,
Size: req.Size,
Type: log.TypeResetSubscribe.Uint8(),
ObjectID: req.UserSubscribeId,
Data: req.Date,
Search: req.Search,
})
if err != nil {
l.Errorf("[FilterResetSubscribeLog] failed to filter system log: %v", err.Error())
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "failed to filter system log: %v", err.Error())
}
var list []types.ResetSubscribeLog
for _, item := range data {
var content log.ResetSubscribe
err = content.Unmarshal([]byte(item.Content))
if err != nil {
l.Errorf("[FilterResetSubscribeLog] failed to unmarshal content: %v", err.Error())
continue
}
list = append(list, types.ResetSubscribeLog{
Type: content.Type,
UserId: content.UserId,
UserSubscribeId: item.ObjectID,
OrderNo: content.OrderNo,
Timestamp: content.Timestamp,
})
}
return &types.FilterResetSubscribeLogResponse{
List: list,
Total: total,
}, nil
}
@@ -0,0 +1,166 @@
package log
import (
"context"
"time"
"github.com/perfect-panel/server/internal/model/log"
"github.com/perfect-panel/server/internal/model/traffic"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
)
type FilterServerTrafficLogLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// NewFilterServerTrafficLogLogic Filter server traffic log
func NewFilterServerTrafficLogLogic(ctx context.Context, svcCtx *svc.ServiceContext) *FilterServerTrafficLogLogic {
return &FilterServerTrafficLogLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *FilterServerTrafficLogLogic) FilterServerTrafficLog(req *types.FilterServerTrafficLogRequest) (resp *types.FilterServerTrafficLogResponse, err error) {
today := time.Now().Format("2006-01-02")
var list []types.ServerTrafficLog
var total int64
if req.Date == today || req.Date == "" {
now := time.Now()
start := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local)
end := start.Add(24 * time.Hour).Add(-time.Nanosecond)
var serverTraffic []log.ServerTraffic
err = l.svcCtx.DB.WithContext(l.ctx).
Model(&traffic.TrafficLog{}).
Select("server_id, SUM(download + upload) AS total, SUM(download) AS download, SUM(upload) AS upload").
Where("timestamp BETWEEN ? AND ?", start, end).
Group("server_id").
Order("SUM(download + upload) DESC").
Scan(&serverTraffic).Error
if err != nil {
l.Errorw("[FilterServerTrafficLog] Query Database Error", logger.Field("error", err.Error()))
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "today traffic query error: %s", err.Error())
}
for _, v := range serverTraffic {
list = append(list, types.ServerTrafficLog{
ServerId: v.ServerId,
Upload: v.Upload,
Download: v.Download,
Total: v.Total,
Date: today,
Details: true,
})
}
todayTotal := len(list)
startIdx := (req.Page - 1) * req.Size
endIdx := startIdx + req.Size
if startIdx < todayTotal {
if endIdx > todayTotal {
endIdx = todayTotal
}
pageData := list[startIdx:endIdx]
return &types.FilterServerTrafficLogResponse{
List: pageData,
Total: int64(todayTotal),
}, nil
}
need := endIdx - todayTotal
historyPage := (need + req.Size - 1) / req.Size // 算出需要的历史页数
historyData, historyTotal, err := l.svcCtx.LogModel.FilterSystemLog(l.ctx, &log.FilterParams{
Page: historyPage,
Size: need,
Type: log.TypeServerTraffic.Uint8(),
})
if err != nil {
l.Errorw("[FilterServerTrafficLog] Query History Error", logger.Field("error", err.Error()))
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "history query error: %s", err.Error())
}
for _, item := range historyData {
var content log.ServerTraffic
if err = content.Unmarshal([]byte(item.Content)); err != nil {
l.Errorw("[FilterServerTrafficLog] Unmarshal Error", logger.Field("error", err.Error()), logger.Field("content", item.Content))
continue
}
hasDetails := true
if l.svcCtx.Config.Log.AutoClear {
last := now.AddDate(0, 0, int(-l.svcCtx.Config.Log.ClearDays))
dataTime, err := time.Parse(time.DateOnly, item.Date)
if err != nil {
l.Errorw("[FilterServerTrafficLog] Parse Date Error", logger.Field("error", err.Error()), logger.Field("date", item.Date))
} else {
if dataTime.Before(last) {
hasDetails = false
} else {
hasDetails = true
}
}
}
list = append(list, types.ServerTrafficLog{
ServerId: item.ObjectID,
Upload: content.Upload,
Download: content.Download,
Total: content.Total,
Date: item.Date,
Details: hasDetails,
})
}
// 返回最终分页数据
if endIdx > len(list) {
endIdx = len(list)
}
pageData := list[startIdx:endIdx]
return &types.FilterServerTrafficLogResponse{
List: pageData,
Total: int64(todayTotal) + historyTotal,
}, nil
}
data, total, err := l.svcCtx.LogModel.FilterSystemLog(l.ctx, &log.FilterParams{
Page: req.Page,
Size: req.Size,
Type: log.TypeServerTraffic.Uint8(),
})
if err != nil {
l.Errorw("[FilterServerTrafficLog] Query Database Error", logger.Field("error", err.Error()))
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "history query error: %s", err.Error())
}
for _, item := range data {
var content log.ServerTraffic
if err = content.Unmarshal([]byte(item.Content)); err != nil {
l.Errorw("[FilterServerTrafficLog] Unmarshal Error", logger.Field("error", err.Error()), logger.Field("content", item.Content))
continue
}
list = append(list, types.ServerTrafficLog{
ServerId: item.ObjectID,
Upload: content.Upload,
Download: content.Download,
Total: content.Total,
Date: item.Date,
Details: false,
})
}
return &types.FilterServerTrafficLogResponse{
List: list,
Total: total,
}, nil
}
@@ -0,0 +1,71 @@
package log
import (
"context"
"strconv"
"github.com/perfect-panel/server/internal/model/log"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
)
type FilterSubscribeLogLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// NewFilterSubscribeLogLogic Filter subscribe log
func NewFilterSubscribeLogLogic(ctx context.Context, svcCtx *svc.ServiceContext) *FilterSubscribeLogLogic {
return &FilterSubscribeLogLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *FilterSubscribeLogLogic) FilterSubscribeLog(req *types.FilterSubscribeLogRequest) (resp *types.FilterSubscribeLogResponse, err error) {
params := &log.FilterParams{
Page: req.Page,
Size: req.Size,
Type: log.TypeSubscribe.Uint8(),
Data: req.Date,
ObjectID: req.UserId,
}
if req.UserSubscribeId != 0 {
params.Search = `"user_subscribe_id":` + strconv.FormatInt(req.UserSubscribeId, 10)
}
data, total, err := l.svcCtx.LogModel.FilterSystemLog(l.ctx, params)
if err != nil {
l.Errorf("[FilterSubscribeLog] failed to filter system log: %v", err.Error())
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "failed to filter system log")
}
var list []types.SubscribeLog
for _, datum := range data {
var content log.Subscribe
err = content.Unmarshal([]byte(datum.Content))
if err != nil {
l.Errorf("[FilterSubscribeLog] failed to unmarshal content: %v", err.Error())
continue
}
list = append(list, types.SubscribeLog{
UserId: datum.ObjectID,
Token: content.Token,
UserAgent: content.UserAgent,
ClientIP: content.ClientIP,
UserSubscribeId: content.UserSubscribeId,
Timestamp: datum.CreatedAt.UnixMilli(),
})
}
return &types.FilterSubscribeLogResponse{
Total: total,
List: list,
}, nil
}
@@ -0,0 +1,84 @@
package log
import (
"context"
"time"
"github.com/perfect-panel/server/internal/model/traffic"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
)
type FilterTrafficLogDetailsLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// NewFilterTrafficLogDetailsLogic Filter traffic log details
func NewFilterTrafficLogDetailsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *FilterTrafficLogDetailsLogic {
return &FilterTrafficLogDetailsLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *FilterTrafficLogDetailsLogic) FilterTrafficLogDetails(req *types.FilterTrafficLogDetailsRequest) (resp *types.FilterTrafficLogDetailsResponse, err error) {
var start, end time.Time
if req.Date != "" {
day, err := time.ParseInLocation("2006-01-02", req.Date, time.Local)
if err != nil {
l.Errorw("[FilterTrafficLogDetails] Date Parse Error", logger.Field("error", err.Error()))
return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), " date parse error: %s", err.Error())
}
start = day
end = day.Add(24*time.Hour - time.Nanosecond)
} else {
// query today
now := time.Now()
start = time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
end = start.Add(24*time.Hour - time.Nanosecond)
}
var data []*traffic.TrafficLog
tx := l.svcCtx.DB.WithContext(l.ctx).Model(&traffic.TrafficLog{})
if req.ServerId != 0 {
tx = tx.Where("server_id = ?", req.ServerId)
}
if !start.IsZero() && !end.IsZero() {
tx = tx.Where("timestamp BETWEEN ? AND ?", start, end)
}
if req.UserId != 0 {
tx = tx.Where("user_id = ?", req.UserId)
}
if req.SubscribeId != 0 {
tx = tx.Where("subscribe_id = ?", req.SubscribeId)
}
var total int64
err = tx.Count(&total).Limit(req.Size).Offset((req.Page - 1) * req.Size).Find(&data).Error
if err != nil {
l.Errorw("[FilterTrafficLogDetails] Query Database Error", logger.Field("error", err.Error()))
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), " database query error: %s", err.Error())
}
var logs []types.TrafficLogDetails
for _, v := range data {
logs = append(logs, types.TrafficLogDetails{
Id: v.Id,
UserId: v.UserId,
ServerId: v.ServerId,
SubscribeId: v.SubscribeId,
Download: v.Download,
Upload: v.Upload,
Timestamp: v.Timestamp.UnixMilli(),
})
}
return &types.FilterTrafficLogDetailsResponse{
List: logs,
Total: total,
}, nil
}
@@ -0,0 +1,160 @@
package log
import (
"context"
"time"
"github.com/perfect-panel/server/internal/model/log"
"github.com/perfect-panel/server/internal/model/traffic"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
)
type FilterUserSubscribeTrafficLogLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// NewFilterUserSubscribeTrafficLogLogic Filter user subscribe traffic log
func NewFilterUserSubscribeTrafficLogLogic(ctx context.Context, svcCtx *svc.ServiceContext) *FilterUserSubscribeTrafficLogLogic {
return &FilterUserSubscribeTrafficLogLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *FilterUserSubscribeTrafficLogLogic) FilterUserSubscribeTrafficLog(req *types.FilterSubscribeTrafficRequest) (resp *types.FilterSubscribeTrafficResponse, err error) {
if req.Size <= 0 {
req.Size = 10
}
if req.Page <= 0 {
req.Page = 1
}
today := time.Now().Format("2006-01-02")
var list []types.UserSubscribeTrafficLog
var total int64
if req.Date == today || req.Date == "" {
now := time.Now()
start := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local)
end := start.Add(24 * time.Hour).Add(-time.Nanosecond)
var userTraffic []types.UserSubscribeTrafficLog
err = l.svcCtx.DB.WithContext(l.ctx).
Model(&traffic.TrafficLog{}).
Select("user_id, subscribe_id, SUM(download + upload) AS total, SUM(download) AS download, SUM(upload) AS upload").
Where("timestamp BETWEEN ? AND ?", start, end).
Group("user_id, subscribe_id").
Order("SUM(download + upload) DESC").
Scan(&userTraffic).Error
if err != nil {
l.Errorw("[FilterUserSubscribeTrafficLog] Query Database Error", logger.Field("error", err.Error()))
return nil, err
}
for _, v := range userTraffic {
list = append(list, types.UserSubscribeTrafficLog{
UserId: v.UserId,
SubscribeId: v.SubscribeId,
Upload: v.Upload,
Download: v.Download,
Total: v.Total,
Date: today,
Details: true,
})
}
todayTotal := len(list)
startIdx := (req.Page - 1) * req.Size
endIdx := startIdx + req.Size
if startIdx < todayTotal {
if endIdx > todayTotal {
endIdx = todayTotal
}
pageData := list[startIdx:endIdx]
return &types.FilterSubscribeTrafficResponse{
List: pageData,
Total: int64(todayTotal),
}, nil
}
need := endIdx - todayTotal
historyPage := (need + req.Size - 1) / req.Size // 算出需要的历史页数
historyData, historyTotal, err := l.svcCtx.LogModel.FilterSystemLog(l.ctx, &log.FilterParams{
Page: historyPage,
Size: need,
Type: log.TypeSubscribeTraffic.Uint8(),
})
if err != nil {
l.Errorw("[FilterUserSubscribeTrafficLog] Query Database Error", logger.Field("error", err.Error()))
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "[FilterUserSubscribeTrafficLog] Query Database Error")
}
for _, datum := range historyData {
var item log.UserTraffic
err = item.Unmarshal([]byte(datum.Content))
if err != nil {
l.Errorw("[FilterUserSubscribeTrafficLog] Unmarshal Content Error", logger.Field("error", err.Error()))
continue
}
list = append(list, types.UserSubscribeTrafficLog{
UserId: item.UserId,
SubscribeId: item.SubscribeId,
Upload: item.Upload,
Download: item.Download,
Total: item.Total,
Date: datum.Date,
Details: false,
})
}
// 返回最终分页数据
if endIdx > len(list) {
endIdx = len(list)
}
pageData := list[startIdx:endIdx]
return &types.FilterSubscribeTrafficResponse{
List: pageData,
Total: int64(todayTotal) + historyTotal,
}, nil
}
var data []*log.SystemLog
data, total, err = l.svcCtx.LogModel.FilterSystemLog(l.ctx, &log.FilterParams{
Page: req.Page,
Size: req.Size,
Type: log.TypeSubscribeTraffic.Uint8(),
Data: req.Date,
})
if err != nil {
l.Errorw("[FilterUserSubscribeTrafficLog] Query Database Error", logger.Field("error", err.Error()))
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "[FilterUserSubscribeTrafficLog] Query Database Error")
}
for _, datum := range data {
var item log.UserTraffic
err = item.Unmarshal([]byte(datum.Content))
if err != nil {
l.Errorw("[FilterUserSubscribeTrafficLog] Unmarshal Content Error", logger.Field("error", err.Error()))
continue
}
list = append(list, types.UserSubscribeTrafficLog{
UserId: item.UserId,
SubscribeId: item.SubscribeId,
Upload: item.Upload,
Download: item.Download,
Total: item.Total,
Date: datum.Date,
Details: false,
})
}
return &types.FilterSubscribeTrafficResponse{
List: list,
Total: total,
}, nil
}
@@ -0,0 +1,37 @@
package log
import (
"context"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/tool"
)
type GetLogSettingLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// Get log setting
func NewGetLogSettingLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetLogSettingLogic {
return &GetLogSettingLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetLogSettingLogic) GetLogSetting() (resp *types.LogSetting, err error) {
configs, err := l.svcCtx.SystemModel.GetLogConfig(l.ctx)
if err != nil {
l.Errorw("[GetLogSetting] Database query error", logger.Field("error", err.Error()))
return nil, err
}
resp = &types.LogSetting{}
// reflect to response
tool.SystemConfigSliceReflectToStruct(configs, resp)
return
}
@@ -7,7 +7,6 @@ import (
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/tool"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
)
@@ -28,20 +27,39 @@ func NewGetMessageLogListLogic(ctx context.Context, svcCtx *svc.ServiceContext)
}
func (l *GetMessageLogListLogic) GetMessageLogList(req *types.GetMessageLogListRequest) (resp *types.GetMessageLogListResponse, err error) {
total, data, err := l.svcCtx.LogModel.FindMessageLogList(l.ctx, req.Page, req.Size, log.MessageLogFilterParams{
Type: req.Type,
Platform: req.Platform,
To: req.To,
Subject: req.Subject,
Content: req.Content,
Status: req.Status,
data, total, err := l.svcCtx.LogModel.FilterSystemLog(l.ctx, &log.FilterParams{
Page: req.Page,
Size: req.Size,
Type: req.Type,
Search: req.Search,
})
if err != nil {
l.Errorw("[GetMessageLogList] Database Error", logger.Field("error", err.Error()))
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "[GetMessageLogList] Database Error: %s", err.Error())
l.Errorf("[GetMessageLogList] failed to filter system log: %v", err.Error())
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "failed to filter system log: %v", err.Error())
}
var list []types.MessageLog
tool.DeepCopy(&list, data)
for _, datum := range data {
var content log.Message
err = content.Unmarshal([]byte(datum.Content))
if err != nil {
l.Errorf("[GetMessageLogList] failed to unmarshal content: %v", err.Error())
continue
}
list = append(list, types.MessageLog{
Id: datum.Id,
Type: datum.Type,
Platform: content.Platform,
To: content.To,
Subject: content.Subject,
Content: content.Content,
Status: content.Status,
CreatedAt: datum.CreatedAt.UnixMilli(),
})
}
return &types.GetMessageLogListResponse{
Total: total,
@@ -0,0 +1,63 @@
package log
import (
"context"
"reflect"
"github.com/perfect-panel/server/internal/config"
"github.com/perfect-panel/server/internal/model/system"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/tool"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
"gorm.io/gorm"
)
type UpdateLogSettingLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// NewUpdateLogSettingLogic Update log setting
func NewUpdateLogSettingLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateLogSettingLogic {
return &UpdateLogSettingLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *UpdateLogSettingLogic) UpdateLogSetting(req *types.LogSetting) error {
v := reflect.ValueOf(*req)
// Get the reflection type of the structure
t := v.Type()
err := l.svcCtx.SystemModel.Transaction(l.ctx, func(db *gorm.DB) error {
var err error
for i := 0; i < v.NumField(); i++ {
// Get the field name
fieldName := t.Field(i).Name
// Get the field value to string
fieldValue := tool.ConvertValueToString(v.Field(i))
// Update the server config
err = db.Model(&system.System{}).Where("`category` = 'log' and `key` = ?", fieldName).Update("value", fieldValue).Error
if err != nil {
break
}
}
return err
})
if err != nil {
l.Errorw("[UpdateLogSetting] update log setting error", logger.Field("error", err.Error()))
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), " update log setting error: %v", err)
}
l.svcCtx.Config.Log = config.Log{
AutoClear: *req.AutoClear,
ClearDays: req.ClearDays,
}
return nil
}
@@ -0,0 +1,170 @@
package marketing
import (
"context"
"strconv"
"strings"
"time"
"github.com/hibiken/asynq"
"github.com/perfect-panel/server/internal/model/task"
"github.com/perfect-panel/server/internal/model/user"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/tool"
"github.com/perfect-panel/server/pkg/xerr"
types2 "github.com/perfect-panel/server/queue/types"
"gorm.io/gorm"
)
type CreateBatchSendEmailTaskLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// NewCreateBatchSendEmailTaskLogic Create a batch send email task
func NewCreateBatchSendEmailTaskLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateBatchSendEmailTaskLogic {
return &CreateBatchSendEmailTaskLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *CreateBatchSendEmailTaskLogic) CreateBatchSendEmailTask(req *types.CreateBatchSendEmailTaskRequest) (err error) {
tx := l.svcCtx.DB
var emails []string
// 通用查询器(含 user JOIN + 注册时间范围过滤)
baseQuery := func() *gorm.DB {
query := tx.Model(&user.AuthMethods{}).
Select("auth_identifier").
Joins("JOIN user ON user.id = user_auth_methods.user_id").
Where("auth_type = ?", "email")
if req.RegisterStartTime != 0 {
query = query.Where("user.created_at >= ?", time.UnixMilli(req.RegisterStartTime))
}
if req.RegisterEndTime != 0 {
query = query.Where("user.created_at <= ?", time.UnixMilli(req.RegisterEndTime))
}
return query
}
var query *gorm.DB
scope := task.ParseScopeType(req.Scope)
switch scope {
case task.ScopeAll:
query = baseQuery()
case task.ScopeActive:
query = baseQuery().
Joins("JOIN user_subscribe ON user.id = user_subscribe.user_id").
Where("user_subscribe.status IN ?", []int64{1, 2})
case task.ScopeExpired:
query = baseQuery().
Joins("JOIN user_subscribe ON user.id = user_subscribe.user_id").
Where("user_subscribe.status = ?", 3)
case task.ScopeNone:
query = baseQuery().
Joins("LEFT JOIN user_subscribe ON user.id = user_subscribe.user_id").
Where("user_subscribe.user_id IS NULL")
default:
}
if query != nil {
// 执行查询
err = query.Pluck("auth_identifier", &emails).Error
if err != nil {
l.Errorf("[CreateBatchSendEmailTask] Failed to fetch email addresses: %v", err.Error())
return xerr.NewErrCode(xerr.DatabaseQueryError)
}
}
// 邮箱列表为空,返回错误
if len(emails) == 0 && scope != task.ScopeSkip {
l.Errorf("[CreateBatchSendEmailTask] No email addresses found for the specified scope")
return xerr.NewErrMsg("No email addresses found for the specified scope")
}
// 邮箱地址去重
emails = tool.RemoveDuplicateElements(emails...)
var additionalEmails []string
// 追加额外的邮箱地址(不覆盖)
if req.Additional != "" {
additionalEmails = tool.RemoveDuplicateElements(strings.Split(req.Additional, "\n")...)
}
if len(additionalEmails) == 0 && scope == task.ScopeSkip {
l.Errorf("[CreateBatchSendEmailTask] No additional email addresses provided for skip scope")
return xerr.NewErrMsg("No additional email addresses provided for skip scope")
}
scheduledAt := time.Now().Add(10 * time.Second) // 默认延迟10秒执行,防止任务创建和执行时间过于接近
if req.Scheduled != 0 {
scheduledAt = time.Unix(req.Scheduled, 0)
if scheduledAt.Before(time.Now()) {
scheduledAt = time.Now()
}
}
scopeInfo := task.EmailScope{
Type: scope.Int8(),
RegisterStartTime: req.RegisterStartTime,
RegisterEndTime: req.RegisterEndTime,
Recipients: emails,
Additional: additionalEmails,
Scheduled: req.Scheduled,
Interval: req.Interval,
Limit: req.Limit,
}
scopeBytes, _ := scopeInfo.Marshal()
taskContent := task.EmailContent{
Subject: req.Subject,
Content: req.Content,
}
contentBytes, _ := taskContent.Marshal()
var total uint64
if additionalEmails != nil {
list := append(emails, additionalEmails...)
total = uint64(len(tool.RemoveDuplicateElements(list...)))
} else {
total = uint64(len(emails))
}
taskInfo := &task.Task{
Type: task.TypeEmail,
Scope: string(scopeBytes),
Content: string(contentBytes),
Status: 0,
Errors: "",
Total: total,
Current: 0,
}
if err = l.svcCtx.DB.Model(&task.Task{}).Create(taskInfo).Error; err != nil {
l.Errorf("[CreateBatchSendEmailTask] Failed to create email task: %v", err.Error())
return xerr.NewErrCode(xerr.DatabaseInsertError)
}
// create task
l.Infof("[CreateBatchSendEmailTask] Successfully created email task with ID: %d", taskInfo.Id)
t := asynq.NewTask(types2.ScheduledBatchSendEmail, []byte(strconv.FormatInt(taskInfo.Id, 10)))
info, err := l.svcCtx.Queue.EnqueueContext(l.ctx, t, asynq.ProcessAt(scheduledAt))
if err != nil {
l.Errorf("[CreateBatchSendEmailTask] Failed to enqueue email task: %v", err.Error())
return xerr.NewErrCode(xerr.QueueEnqueueError)
}
l.Infof("[CreateBatchSendEmailTask] Successfully enqueued email task with ID: %s, scheduled at: %s", info.ID, scheduledAt.Format(time.DateTime))
return nil
}
@@ -0,0 +1,104 @@
package marketing
import (
"context"
"strconv"
"time"
"github.com/hibiken/asynq"
"github.com/perfect-panel/server/internal/model/task"
"github.com/perfect-panel/server/internal/model/user"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
queueType "github.com/perfect-panel/server/queue/types"
"github.com/pkg/errors"
)
type CreateQuotaTaskLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// NewCreateQuotaTaskLogic Create a quota task
func NewCreateQuotaTaskLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateQuotaTaskLogic {
return &CreateQuotaTaskLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *CreateQuotaTaskLogic) CreateQuotaTask(req *types.CreateQuotaTaskRequest) error {
var subs []*user.Subscribe
query := l.svcCtx.DB.WithContext(l.ctx).Model(&user.Subscribe{})
if len(req.Subscribers) > 0 {
query = query.Where("`subscribe_id` IN ?", req.Subscribers)
}
if req.IsActive != nil && *req.IsActive {
query = query.Where("`status` IN ?", []int64{0, 1, 2}) // 0: Pending 1: Active 2: Finished
}
if req.StartTime != 0 {
start := time.UnixMilli(req.StartTime)
query = query.Where("`start_time` <= ?", start)
}
if req.EndTime != 0 {
end := time.UnixMilli(req.EndTime)
query = query.Where("`expire_time` >= ?", end)
}
if err := query.Find(&subs).Error; err != nil {
l.Errorf("[CreateQuotaTask] find subscribers error: %v", err.Error())
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find subscribers error")
}
if len(subs) == 0 {
return errors.Wrapf(xerr.NewErrMsg("No subscribers found"), "no subscribers found")
}
var subIds []int64
for _, sub := range subs {
subIds = append(subIds, sub.Id)
}
scopeInfo := task.QuotaScope{
Subscribers: req.Subscribers,
IsActive: req.IsActive,
StartTime: req.StartTime,
EndTime: req.EndTime,
Objects: subIds,
}
scopeBytes, _ := scopeInfo.Marshal()
contentInfo := task.QuotaContent{
ResetTraffic: req.ResetTraffic,
Days: req.Days,
GiftType: req.GiftType,
GiftValue: req.GiftValue,
}
contentBytes, _ := contentInfo.Marshal()
// create task
newTask := &task.Task{
Type: task.TypeQuota,
Status: 0,
Scope: string(scopeBytes),
Content: string(contentBytes),
Total: uint64(len(subIds)),
Current: 0,
Errors: "",
}
if err := l.svcCtx.DB.WithContext(l.ctx).Model(&task.Task{}).Create(newTask).Error; err != nil {
l.Errorf("[CreateQuotaTask] create task error: %v", err.Error())
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "create task error")
}
// enqueue task
queueTask := asynq.NewTask(queueType.ForthwithQuotaTask, []byte(strconv.FormatInt(newTask.Id, 10)))
if _, err := l.svcCtx.Queue.EnqueueContext(l.ctx, queueTask); err != nil {
l.Errorf("[CreateQuotaTask] enqueue task error: %v", err.Error())
return errors.Wrapf(xerr.NewErrCode(xerr.QueueEnqueueError), "enqueue task error")
}
logger.Infof("[CreateQuotaTask] Successfully created task with ID: %d", newTask.Id)
return nil
}
@@ -0,0 +1,89 @@
package marketing
import (
"context"
"strings"
"github.com/perfect-panel/server/internal/model/task"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
)
type GetBatchSendEmailTaskListLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// NewGetBatchSendEmailTaskListLogic Get batch send email task list
func NewGetBatchSendEmailTaskListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetBatchSendEmailTaskListLogic {
return &GetBatchSendEmailTaskListLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetBatchSendEmailTaskListLogic) GetBatchSendEmailTaskList(req *types.GetBatchSendEmailTaskListRequest) (resp *types.GetBatchSendEmailTaskListResponse, err error) {
var tasks []*task.Task
tx := l.svcCtx.DB.Model(&task.Task{}).Where("`type` = ?", task.TypeEmail)
if req.Status != nil {
tx = tx.Where("status = ?", *req.Status)
}
if req.Scope != nil {
tx = tx.Where("scope = ?", req.Scope)
}
if req.Page == 0 {
req.Page = 1
}
if req.Size == 0 {
req.Size = 10
}
err = tx.Offset((req.Page - 1) * req.Size).Limit(req.Size).Order("created_at DESC").Find(&tasks).Error
if err != nil {
l.Errorf("failed to get email tasks: %v", err)
return nil, xerr.NewErrCode(xerr.DatabaseQueryError)
}
list := make([]types.BatchSendEmailTask, 0)
for _, t := range tasks {
var scopeInfo task.EmailScope
if err = scopeInfo.Unmarshal([]byte(t.Scope)); err != nil {
l.Errorf("[GetBatchSendEmailTaskList] failed to unmarshal email task scope: %v", err.Error())
continue
}
var contentInfo task.EmailContent
if err = contentInfo.Unmarshal([]byte(t.Content)); err != nil {
l.Errorf("[GetBatchSendEmailTaskList] failed to unmarshal email task content: %v", err.Error())
continue
}
list = append(list, types.BatchSendEmailTask{
Id: t.Id,
Subject: contentInfo.Subject,
Content: contentInfo.Content,
Recipients: strings.Join(scopeInfo.Recipients, "\n"),
Scope: scopeInfo.Type,
RegisterStartTime: scopeInfo.RegisterStartTime,
RegisterEndTime: scopeInfo.RegisterEndTime,
Additional: strings.Join(scopeInfo.Additional, "\n"),
Scheduled: scopeInfo.Scheduled,
Interval: scopeInfo.Interval,
Limit: scopeInfo.Limit,
Status: uint8(t.Status),
Errors: t.Errors,
Total: t.Total,
Current: t.Current,
CreatedAt: t.CreatedAt.UnixMilli(),
UpdatedAt: t.UpdatedAt.UnixMilli(),
})
}
return &types.GetBatchSendEmailTaskListResponse{
List: list,
}, nil
}
@@ -0,0 +1,44 @@
package marketing
import (
"context"
"github.com/perfect-panel/server/internal/model/task"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
)
type GetBatchSendEmailTaskStatusLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// NewGetBatchSendEmailTaskStatusLogic Get batch send email task status
func NewGetBatchSendEmailTaskStatusLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetBatchSendEmailTaskStatusLogic {
return &GetBatchSendEmailTaskStatusLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetBatchSendEmailTaskStatusLogic) GetBatchSendEmailTaskStatus(req *types.GetBatchSendEmailTaskStatusRequest) (resp *types.GetBatchSendEmailTaskStatusResponse, err error) {
tx := l.svcCtx.DB
var taskInfo *task.Task
err = tx.Model(&task.Task{}).Where("id = ?", req.Id).First(&taskInfo).Error
if err != nil {
l.Errorf("failed to get email task status, error: %v", err)
return nil, xerr.NewErrCode(xerr.DatabaseQueryError)
}
return &types.GetBatchSendEmailTaskStatusResponse{
Status: uint8(taskInfo.Status),
Total: int64(taskInfo.Total),
Current: int64(taskInfo.Current),
Errors: taskInfo.Errors,
}, nil
}
@@ -0,0 +1,93 @@
package marketing
import (
"context"
"time"
"github.com/perfect-panel/server/internal/model/task"
"github.com/perfect-panel/server/internal/model/user"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
"gorm.io/gorm"
)
type GetPreSendEmailCountLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// NewGetPreSendEmailCountLogic Get pre-send email count
func NewGetPreSendEmailCountLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetPreSendEmailCountLogic {
return &GetPreSendEmailCountLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetPreSendEmailCountLogic) GetPreSendEmailCount(req *types.GetPreSendEmailCountRequest) (resp *types.GetPreSendEmailCountResponse, err error) {
tx := l.svcCtx.DB
var count int64
// 通用查询器(含 user JOIN + 注册时间范围过滤)
baseQuery := func() *gorm.DB {
query := tx.Model(&user.AuthMethods{}).
Select("auth_identifier").
Joins("JOIN user ON user.id = user_auth_methods.user_id").
Where("auth_type = ?", "email")
if req.RegisterStartTime != 0 {
registerStartTime := time.UnixMilli(req.RegisterStartTime)
query = query.Where("user.created_at >= ?", registerStartTime)
}
if req.RegisterEndTime != 0 {
registerEndTime := time.UnixMilli(req.RegisterEndTime)
query = query.Where("user.created_at <= ?", registerEndTime)
}
return query
}
var query *gorm.DB
scope := task.ParseScopeType(req.Scope)
switch scope {
case task.ScopeAll:
query = baseQuery()
case task.ScopeActive:
query = baseQuery().
Joins("JOIN user_subscribe ON user.id = user_subscribe.user_id").
Where("user_subscribe.status IN ?", []int64{1, 2})
case task.ScopeExpired:
query = baseQuery().
Joins("JOIN user_subscribe ON user.id = user_subscribe.user_id").
Where("user_subscribe.status = ?", 3)
case task.ScopeNone:
query = baseQuery().
Joins("LEFT JOIN user_subscribe ON user.id = user_subscribe.user_id").
Where("user_subscribe.user_id IS NULL")
case task.ScopeSkip:
// Skip scope does not require a count
query = nil
default:
l.Errorf("[CreateBatchSendEmailTask] Invalid scope: %v", req.Scope)
return nil, xerr.NewErrMsg("Invalid email scope")
}
if query != nil {
if err = query.Count(&count).Error; err != nil {
l.Errorf("[GetPreSendEmailCount] Count error: %v", err)
return nil, xerr.NewErrMsg("Failed to count emails")
}
}
return &types.GetPreSendEmailCountResponse{
Count: count,
}, nil
}
@@ -0,0 +1,83 @@
package marketing
import (
"context"
"github.com/perfect-panel/server/internal/model/task"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
)
type QueryQuotaTaskListLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// NewQueryQuotaTaskListLogic Query quota task list
func NewQueryQuotaTaskListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryQuotaTaskListLogic {
return &QueryQuotaTaskListLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *QueryQuotaTaskListLogic) QueryQuotaTaskList(req *types.QueryQuotaTaskListRequest) (resp *types.QueryQuotaTaskListResponse, err error) {
var data []*task.Task
var count int64
query := l.svcCtx.DB.Model(&task.Task{}).Where("`type` = ?", task.TypeQuota)
if req.Page == 0 {
req.Page = 1
}
if req.Size == 0 {
req.Size = 20
}
if req.Status != nil {
query = query.Where("`status` = ?", *req.Status)
}
err = query.Count(&count).Offset((req.Page - 1) * req.Size).Limit(req.Size).Order("created_at DESC").Find(&data).Error
if err != nil {
l.Errorf("[QueryQuotaTaskList] failed to get quota tasks: %v", err)
return nil, err
}
var list []types.QuotaTask
for _, item := range data {
var scopeInfo task.QuotaScope
if err = scopeInfo.Unmarshal([]byte(item.Scope)); err != nil {
l.Errorf("[QueryQuotaTaskList] failed to unmarshal quota task scope: %v", err.Error())
continue
}
var contentInfo task.QuotaContent
if err = contentInfo.Unmarshal([]byte(item.Content)); err != nil {
l.Errorf("[QueryQuotaTaskList] failed to unmarshal quota task content: %v", err.Error())
continue
}
list = append(list, types.QuotaTask{
Id: item.Id,
Subscribers: scopeInfo.Subscribers,
IsActive: scopeInfo.IsActive,
StartTime: scopeInfo.StartTime,
EndTime: scopeInfo.EndTime,
ResetTraffic: contentInfo.ResetTraffic,
Days: contentInfo.Days,
GiftType: contentInfo.GiftType,
GiftValue: contentInfo.GiftValue,
Objects: scopeInfo.Objects,
Status: uint8(item.Status),
Total: int64(item.Total),
Current: int64(item.Current),
Errors: item.Errors,
CreatedAt: item.CreatedAt.UnixMilli(),
UpdatedAt: item.UpdatedAt.UnixMilli(),
})
}
return &types.QueryQuotaTaskListResponse{
Total: count,
List: list,
}, nil
}
@@ -0,0 +1,55 @@
package marketing
import (
"context"
"time"
"github.com/perfect-panel/server/internal/model/user"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
)
type QueryQuotaTaskPreCountLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// NewQueryQuotaTaskPreCountLogic Query quota task pre-count
func NewQueryQuotaTaskPreCountLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryQuotaTaskPreCountLogic {
return &QueryQuotaTaskPreCountLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *QueryQuotaTaskPreCountLogic) QueryQuotaTaskPreCount(req *types.QueryQuotaTaskPreCountRequest) (resp *types.QueryQuotaTaskPreCountResponse, err error) {
tx := l.svcCtx.DB.WithContext(l.ctx).Model(&user.Subscribe{})
var count int64
if len(req.Subscribers) > 0 {
tx = tx.Where("`subscribe_id` IN ?", req.Subscribers)
}
if req.IsActive != nil && *req.IsActive {
tx = tx.Where("`status` IN ?", []int64{0, 1, 2}) // 0: Pending 1: Active 2: Finished
}
if req.StartTime != 0 {
start := time.UnixMilli(req.StartTime)
tx = tx.Where("`start_time` <= ?", start)
}
if req.EndTime != 0 {
end := time.UnixMilli(req.EndTime)
tx = tx.Where("`expire_time` >= ?", end)
}
if err = tx.Count(&count).Error; err != nil {
l.Errorf("[QueryQuotaTaskPreCount] count error: %v", err.Error())
return nil, err
}
return &types.QueryQuotaTaskPreCountResponse{
Count: count,
}, nil
}
@@ -0,0 +1,42 @@
package marketing
import (
"context"
"github.com/perfect-panel/server/internal/model/task"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
)
type QueryQuotaTaskStatusLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// NewQueryQuotaTaskStatusLogic Query quota task status
func NewQueryQuotaTaskStatusLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryQuotaTaskStatusLogic {
return &QueryQuotaTaskStatusLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *QueryQuotaTaskStatusLogic) QueryQuotaTaskStatus(req *types.QueryQuotaTaskStatusRequest) (resp *types.QueryQuotaTaskStatusResponse, err error) {
var data *task.Task
err = l.svcCtx.DB.Model(&task.Task{}).Where("id = ? AND `type` = ?", req.Id, task.TypeQuota).First(&data).Error
if err != nil {
l.Errorf("[QueryQuotaTaskStatus] failed to get quota task: %v", err.Error())
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), " failed to get quota task: %v", err.Error())
}
return &types.QueryQuotaTaskStatusResponse{
Status: uint8(data.Status),
Current: int64(data.Current),
Total: int64(data.Total),
Errors: data.Errors,
}, nil
}
@@ -0,0 +1,42 @@
package marketing
import (
"context"
"github.com/perfect-panel/server/internal/model/task"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/email"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
)
type StopBatchSendEmailTaskLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// NewStopBatchSendEmailTaskLogic Stop a batch send email task
func NewStopBatchSendEmailTaskLogic(ctx context.Context, svcCtx *svc.ServiceContext) *StopBatchSendEmailTaskLogic {
return &StopBatchSendEmailTaskLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *StopBatchSendEmailTaskLogic) StopBatchSendEmailTask(req *types.StopBatchSendEmailTaskRequest) (err error) {
if email.Manager != nil {
email.Manager.RemoveWorker(req.Id)
} else {
logger.Error("[StopBatchSendEmailTaskLogic] email.Manager is nil, cannot stop task")
}
err = l.svcCtx.DB.Model(&task.Task{}).Where("id = ?", req.Id).Update("status", 2).Error
if err != nil {
l.Errorf("failed to stop email task, error: %v", err)
return xerr.NewErrCode(xerr.DatabaseUpdateError)
}
return
}
@@ -55,10 +55,9 @@ func (l *CreatePaymentMethodLogic) CreatePaymentMethod(req *types.CreatePaymentM
Token: random.KeyNew(8, 1),
}
err = l.svcCtx.PaymentModel.Transaction(l.ctx, func(tx *gorm.DB) error {
if req.Platform == "Stripe" {
var cfg paymentModel.StripeConfig
if err := cfg.Unmarshal(paymentMethod.Config); err != nil {
if err = cfg.Unmarshal([]byte(paymentMethod.Config)); err != nil {
l.Errorf("[CreatePaymentMethod] unmarshal stripe config error: %s", err.Error())
return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "unmarshal stripe config error: %s", err.Error())
}
@@ -79,7 +78,8 @@ func (l *CreatePaymentMethodLogic) CreatePaymentMethod(req *types.CreatePaymentM
return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "create stripe webhook endpoint error: %s", err.Error())
}
cfg.WebhookSecret = endpoint.Secret
paymentMethod.Config = cfg.Marshal()
content, _ := cfg.Marshal()
paymentMethod.Config = string(content)
}
if err = tx.Model(&paymentModel.Payment{}).Create(paymentMethod).Error; err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "insert payment method error: %s", err.Error())
@@ -101,27 +101,36 @@ func (l *CreatePaymentMethodLogic) CreatePaymentMethod(req *types.CreatePaymentM
func parsePaymentPlatformConfig(ctx context.Context, platform payment.Platform, config interface{}) string {
data, err := json.Marshal(config)
if err != nil {
logger.WithContext(ctx).Errorw("parse payment platform config error", logger.Field("platform", platform), logger.Field("config", config), logger.Field("error", err.Error()))
logger.WithContext(ctx).Errorw("marshal config error", logger.Field("platform", platform), logger.Field("config", config), logger.Field("error", err.Error()))
return ""
}
// 通用处理函数
handleConfig := func(name string, target interface {
Unmarshal([]byte) error
Marshal() ([]byte, error)
}) string {
if err = target.Unmarshal(data); err != nil {
logger.WithContext(ctx).Errorw("parse "+name+" config error", logger.Field("config", string(data)), logger.Field("error", err.Error()))
return ""
}
content, err := target.Marshal()
if err != nil {
logger.WithContext(ctx).Errorw("marshal "+name+" config error", logger.Field("error", err.Error()))
return ""
}
return string(content)
}
switch platform {
case payment.Stripe:
stripe := &paymentModel.StripeConfig{}
if err := stripe.Unmarshal(string(data)); err != nil {
logger.WithContext(ctx).Errorw("parse stripe config error", logger.Field("config", string(data)), logger.Field("error", err.Error()))
}
return stripe.Marshal()
return handleConfig("Stripe", &paymentModel.StripeConfig{})
case payment.AlipayF2F:
alipay := &paymentModel.AlipayF2FConfig{}
if err := alipay.Unmarshal(string(data)); err != nil {
logger.WithContext(ctx).Errorw("parse alipay config error", logger.Field("config", string(data)), logger.Field("error", err.Error()))
}
return alipay.Marshal()
return handleConfig("Alipay", &paymentModel.AlipayF2FConfig{})
case payment.EPay:
epay := &paymentModel.EPayConfig{}
if err := epay.Unmarshal(string(data)); err != nil {
logger.WithContext(ctx).Errorw("parse epay config error", logger.Field("config", string(data)), logger.Field("error", err.Error()))
}
return epay.Marshal()
return handleConfig("Epay", &paymentModel.EPayConfig{})
case payment.CryptoSaaS:
return handleConfig("CryptoSaaS", &paymentModel.CryptoSaaSConfig{})
default:
return ""
}
@@ -55,17 +55,18 @@ func (l *GetPaymentMethodListLogic) GetPaymentMethodList(req *types.GetPaymentMe
}
}
resp.List[i] = types.PaymentMethodDetail{
Id: v.Id,
Name: v.Name,
Platform: v.Platform,
Icon: v.Icon,
Domain: v.Domain,
Config: config,
FeeMode: v.FeeMode,
FeePercent: v.FeePercent,
FeeAmount: v.FeeAmount,
Enable: *v.Enable,
NotifyURL: notifyUrl,
Id: v.Id,
Name: v.Name,
Platform: v.Platform,
Icon: v.Icon,
Domain: v.Domain,
Config: config,
FeeMode: v.FeeMode,
FeePercent: v.FeePercent,
FeeAmount: v.FeeAmount,
Enable: *v.Enable,
NotifyURL: notifyUrl,
Description: v.Description,
}
}
return
@@ -19,7 +19,7 @@ type UpdatePaymentMethodLogic struct {
svcCtx *svc.ServiceContext
}
// Update Payment Method
// NewUpdatePaymentMethodLogic Update Payment Method
func NewUpdatePaymentMethodLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdatePaymentMethodLogic {
return &UpdatePaymentMethodLogic{
Logger: logger.WithContext(ctx),
@@ -1,44 +0,0 @@
package server
import (
"context"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
)
type BatchDeleteNodeGroupLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewBatchDeleteNodeGroupLogic(ctx context.Context, svcCtx *svc.ServiceContext) *BatchDeleteNodeGroupLogic {
return &BatchDeleteNodeGroupLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *BatchDeleteNodeGroupLogic) BatchDeleteNodeGroup(req *types.BatchDeleteNodeGroupRequest) error {
// Check if the group is empty
count, err := l.svcCtx.ServerModel.QueryServerCountByServerGroups(l.ctx, req.Ids)
if err != nil {
l.Errorw("[BatchDeleteNodeGroup] Query Database Error: ", logger.Field("error", err.Error()))
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query server error: %v", err)
}
if count > 0 {
return errors.Wrapf(xerr.NewErrCode(xerr.NodeGroupNotEmpty), "group is not empty")
}
// Delete the group
err = l.svcCtx.ServerModel.BatchDeleteNodeGroup(l.ctx, req.Ids)
if err != nil {
l.Errorw("[BatchDeleteNodeGroup] Delete Database Error: ", logger.Field("error", err.Error()))
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseDeletedError), err.Error())
}
return nil
}
@@ -1,43 +0,0 @@
package server
import (
"context"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
"gorm.io/gorm"
)
type BatchDeleteNodeLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewBatchDeleteNodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *BatchDeleteNodeLogic {
return &BatchDeleteNodeLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *BatchDeleteNodeLogic) BatchDeleteNode(req *types.BatchDeleteNodeRequest) error {
err := l.svcCtx.DB.Transaction(func(db *gorm.DB) error {
for _, id := range req.Ids {
err := l.svcCtx.ServerModel.Delete(l.ctx, id)
if err != nil {
return err
}
}
return nil
})
if err != nil {
l.Errorw("[BatchDeleteNode] Delete Database Error: ", logger.Field("error", err.Error()))
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseDeletedError), err.Error())
}
return nil
}
+11
View File
@@ -0,0 +1,11 @@
package server
const (
ShadowSocks = "shadowsocks"
Vmess = "vmess"
Vless = "vless"
Trojan = "trojan"
AnyTLS = "anytls"
Tuic = "tuic"
Hysteria2 = "hysteria2"
)
@@ -1,40 +0,0 @@
package server
import (
"context"
"github.com/perfect-panel/server/internal/model/server"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/pkg/errors"
)
type CreateNodeGroupLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewCreateNodeGroupLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateNodeGroupLogic {
return &CreateNodeGroupLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *CreateNodeGroupLogic) CreateNodeGroup(req *types.CreateNodeGroupRequest) error {
groupInfo := &server.Group{
Name: req.Name,
Description: req.Description,
}
err := l.svcCtx.ServerModel.InsertGroup(l.ctx, groupInfo)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), err.Error())
}
return nil
}
+11 -94
View File
@@ -2,18 +2,13 @@ package server
import (
"context"
"encoding/json"
"strings"
"time"
"github.com/hibiken/asynq"
"github.com/perfect-panel/server/internal/model/server"
"github.com/perfect-panel/server/internal/model/node"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/tool"
"github.com/perfect-panel/server/pkg/xerr"
queue "github.com/perfect-panel/server/queue/types"
"github.com/pkg/errors"
)
@@ -23,6 +18,7 @@ type CreateNodeLogic struct {
svcCtx *svc.ServiceContext
}
// NewCreateNodeLogic Create Node
func NewCreateNodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateNodeLogic {
return &CreateNodeLogic{
Logger: logger.WithContext(ctx),
@@ -32,97 +28,18 @@ func NewCreateNodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Create
}
func (l *CreateNodeLogic) CreateNode(req *types.CreateNodeRequest) error {
config, err := json.Marshal(req.Config)
if err != nil {
return err
data := node.Node{
Name: req.Name,
Tags: tool.StringSliceToString(req.Tags),
Port: req.Port,
Address: req.Address,
ServerId: req.ServerId,
Protocol: req.Protocol,
}
var serverInfo server.Server
tool.DeepCopy(&serverInfo, req)
serverInfo.Config = string(config)
nodeRelay, err := json.Marshal(req.RelayNode)
if err != nil {
l.Errorw("[UpdateNode] Marshal RelayNode Error: ", logger.Field("error", err.Error()))
return err
}
if len(req.Tags) > 0 {
serverInfo.Tags = strings.Join(req.Tags, ",")
}
serverInfo.LastReportedAt = time.UnixMicro(1218124800)
serverInfo.City = req.City
serverInfo.Country = req.Country
serverInfo.RelayNode = string(nodeRelay)
if req.Protocol == "vless" {
var cfg types.Vless
if err = json.Unmarshal(config, &cfg); err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "json.Unmarshal error: %v", err.Error())
}
if cfg.Security == "reality" && cfg.SecurityConfig.RealityPublicKey == "" {
public, private, err := tool.Curve25519Genkey(false, "")
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "generate curve25519 key error")
}
cfg.SecurityConfig.RealityPublicKey = public
cfg.SecurityConfig.RealityPrivateKey = private
cfg.SecurityConfig.RealityShortId = tool.GenerateShortID(private)
}
if cfg.SecurityConfig.RealityServerAddr == "" {
cfg.SecurityConfig.RealityServerAddr = cfg.SecurityConfig.SNI
}
if cfg.SecurityConfig.RealityServerPort == 0 {
cfg.SecurityConfig.RealityServerPort = 443
}
config, _ = json.Marshal(cfg)
serverInfo.Config = string(config)
} else if req.Protocol == "shadowsocks" {
var cfg types.Shadowsocks
if err = json.Unmarshal(config, &cfg); err != nil {
l.Errorf("[CreateNode] Unmarshal Shadowsocks Config Error: %v", err.Error())
return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "json.Unmarshal error: %v", err.Error())
}
if strings.Contains(cfg.Method, "2022") {
var length int
switch cfg.Method {
case "2022-blake3-aes-128-gcm":
length = 16
default:
length = 32
}
if len(cfg.ServerKey) != length {
cfg.ServerKey = tool.GenerateCipher(cfg.ServerKey, length)
}
}
config, _ = json.Marshal(cfg)
serverInfo.Config = string(config)
}
err = l.svcCtx.ServerModel.Insert(l.ctx, &serverInfo)
err := l.svcCtx.NodeModel.InsertNode(l.ctx, &data)
if err != nil {
l.Errorw("[CreateNode] Insert Database Error: ", logger.Field("error", err.Error()))
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "create server error: %v", err)
}
if req.City == "" || req.Country == "" {
// Marshal the task payload
payload, err := json.Marshal(queue.GetNodeCountry{
Protocol: serverInfo.Protocol,
ServerAddr: serverInfo.ServerAddr,
})
if err != nil {
l.Errorw("[GetNodeCountry]: Marshal Error", logger.Field("error", err.Error()))
return errors.Wrap(xerr.NewErrCode(xerr.ERROR), "Failed to marshal task payload")
}
// Create a queue task
task := asynq.NewTask(queue.ForthwithGetCountry, payload)
// Enqueue the task
taskInfo, err := l.svcCtx.Queue.Enqueue(task)
if err != nil {
l.Errorw("[GetNodeCountry]: Enqueue Error", logger.Field("error", err.Error()), logger.Field("payload", string(payload)))
return errors.Wrap(xerr.NewErrCode(xerr.ERROR), "Failed to enqueue task")
}
l.Infow("[GetNodeCountry]: Enqueue Success", logger.Field("taskID", taskInfo.ID), logger.Field("payload", string(payload)))
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "[CreateNode] Insert Database Error")
}
return nil
@@ -1,78 +0,0 @@
package server
import (
"context"
"strings"
"github.com/perfect-panel/server/pkg/rules"
"github.com/perfect-panel/server/internal/model/server"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/tool"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
)
type CreateRuleGroupLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// Create rule group
func NewCreateRuleGroupLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateRuleGroupLogic {
return &CreateRuleGroupLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func parseAndValidateRules(ruleText, ruleName string) ([]string, error) {
var rs []string
ruleArr := strings.Split(ruleText, "\n")
if len(ruleArr) == 0 {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "rules is empty")
}
for _, s := range ruleArr {
r := rules.NewRule(s, ruleName)
if r == nil {
continue
}
if err := r.Validate(); err != nil {
continue
}
rs = append(rs, r.String())
}
return rs, nil
}
func (l *CreateRuleGroupLogic) CreateRuleGroup(req *types.CreateRuleGroupRequest) error {
rs, err := parseAndValidateRules(req.Rules, req.Name)
if err != nil {
return err
}
info := &server.RuleGroup{
Name: req.Name,
Icon: req.Icon,
Type: req.Type,
Tags: tool.StringSliceToString(req.Tags),
Rules: strings.Join(rs, "\n"),
Default: req.Default,
Enable: req.Enable,
}
err = l.svcCtx.ServerModel.InsertRuleGroup(l.ctx, info)
if err != nil {
l.Errorw("[CreateRuleGroup] Insert Database Error: ", logger.Field("error", err.Error()))
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "create server rule group error: %v", err)
}
if req.Default {
if err = l.svcCtx.ServerModel.SetDefaultRuleGroup(l.ctx, info.Id); err != nil {
l.Errorw("[CreateRuleGroup] Set Default Rule Group Error: ", logger.Field("error", err.Error()))
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "set default rule group error: %v", err)
}
}
return nil
}
@@ -0,0 +1,110 @@
package server
import (
"context"
"strings"
"github.com/perfect-panel/server/internal/model/node"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/ip"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/tool"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
)
type CreateServerLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// NewCreateServerLogic Create Server
func NewCreateServerLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateServerLogic {
return &CreateServerLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *CreateServerLogic) CreateServer(req *types.CreateServerRequest) error {
data := node.Server{
Name: req.Name,
Country: req.Country,
City: req.City,
Address: req.Address,
Sort: req.Sort,
Protocols: "",
}
protocols := make([]node.Protocol, 0)
for _, item := range req.Protocols {
if item.Type == "" {
return errors.Wrapf(xerr.NewErrCodeMsg(xerr.InvalidParams, "protocols type is empty"), "protocols type is empty")
}
var protocol node.Protocol
tool.DeepCopy(&protocol, item)
// VLESS Reality Key Generation
if protocol.Type == "vless" {
if protocol.Security == "reality" {
if protocol.RealityPublicKey == "" {
public, private, err := tool.Curve25519Genkey(false, "")
if err != nil {
l.Errorf("[CreateServer] Generate Reality Key Error: %v", err.Error())
return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "generate reality key error: %v", err)
}
protocol.RealityPublicKey = public
protocol.RealityPrivateKey = private
protocol.RealityShortId = tool.GenerateShortID(private)
}
if protocol.RealityServerAddr == "" {
protocol.RealityServerAddr = protocol.SNI
}
if protocol.RealityServerPort == 0 {
protocol.RealityServerPort = 443
}
}
}
// ShadowSocks 2022 Key Generation
if protocol.Type == "shadowsocks" {
if strings.Contains(protocol.Cipher, "2022") {
var length int
switch protocol.Cipher {
case "2022-blake3-aes-128-gcm":
length = 16
default:
length = 32
}
if len(protocol.ServerKey) != length {
protocol.ServerKey = tool.GenerateCipher(protocol.ServerKey, length)
}
}
}
protocols = append(protocols, protocol)
}
err := data.MarshalProtocols(protocols)
if err != nil {
l.Errorf("[CreateServer] Marshal Protocols Error: %v", err.Error())
return errors.Wrapf(xerr.NewErrCodeMsg(xerr.InvalidParams, "protocols marshal error"), "protocols marshal error: %v", err)
}
if data.City == "" && data.Country == "" {
// query server ip location
result, err := ip.GetRegionByIp(req.Address)
if err != nil {
l.Errorf("[CreateServer] GetRegionByIp Error: %v", err.Error())
} else {
data.City = result.City
data.Country = result.Country
}
}
err = l.svcCtx.NodeModel.InsertServer(l.ctx, &data)
if err != nil {
l.Errorf("[CreateServer] Insert Server error: %v", err.Error())
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "insert server error: %v", err)
}
return nil
}
@@ -1,44 +0,0 @@
package server
import (
"context"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
)
type DeleteNodeGroupLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewDeleteNodeGroupLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeleteNodeGroupLogic {
return &DeleteNodeGroupLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *DeleteNodeGroupLogic) DeleteNodeGroup(req *types.DeleteNodeGroupRequest) error {
// Check if the group is empty
count, err := l.svcCtx.ServerModel.QueryServerCountByServerGroups(l.ctx, []int64{req.Id})
if err != nil {
l.Errorw("[DeleteNodeGroup] Query Database Error: ", logger.Field("error", err.Error()))
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query server error: %v", err)
}
if count > 0 {
return errors.Wrapf(xerr.NewErrCode(xerr.NodeGroupNotEmpty), "group is not empty")
}
// Delete the group
err = l.svcCtx.ServerModel.DeleteGroup(l.ctx, req.Id)
if err != nil {
l.Errorw("[DeleteNodeGroup] Delete Database Error: ", logger.Field("error", err.Error()))
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseDeletedError), err.Error())
}
return nil
}
+15 -27
View File
@@ -2,14 +2,14 @@ package server
import (
"context"
"github.com/perfect-panel/server/pkg/tool"
"strings"
"github.com/perfect-panel/server/internal/model/node"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
"gorm.io/gorm"
)
type DeleteNodeLogic struct {
@@ -18,6 +18,7 @@ type DeleteNodeLogic struct {
svcCtx *svc.ServiceContext
}
// NewDeleteNodeLogic Delete Node
func NewDeleteNodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeleteNodeLogic {
return &DeleteNodeLogic{
Logger: logger.WithContext(ctx),
@@ -27,33 +28,20 @@ func NewDeleteNodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Delete
}
func (l *DeleteNodeLogic) DeleteNode(req *types.DeleteNodeRequest) error {
err := l.svcCtx.DB.Transaction(func(tx *gorm.DB) error {
// Delete server
err := l.svcCtx.ServerModel.Delete(l.ctx, req.Id, tx)
if err != nil {
return err
}
// Delete server to subscribe
subs, err := l.svcCtx.SubscribeModel.QuerySubscribeIdsByServerIdAndServerGroupId(l.ctx, req.Id, 0)
if err != nil {
l.Logger.Errorf("[DeleteNode] QuerySubscribeIdsByServerIdAndServerGroupId error: %v", err.Error())
return err
}
data, err := l.svcCtx.NodeModel.FindOneNode(l.ctx, req.Id)
for _, sub := range subs {
servers := tool.StringToInt64Slice(sub.Server)
newServers := tool.RemoveElementBySlice(servers, req.Id)
sub.Server = tool.Int64SliceToString(newServers)
if err = l.svcCtx.SubscribeModel.Update(l.ctx, sub, tx); err != nil {
l.Logger.Errorf("[DeleteNode] UpdateSubscribe error: %v", err.Error())
return err
}
}
return nil
})
err = l.svcCtx.NodeModel.DeleteNode(l.ctx, req.Id)
if err != nil {
l.Errorw("[DeleteNode] Delete Database Error: ", logger.Field("error", err.Error()))
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseDeletedError), "delete server error: %v", err)
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseDeletedError), "[DeleteNode] Delete Database Error")
}
return nil
return l.svcCtx.NodeModel.ClearNodeCache(l.ctx, &node.FilterNodeParams{
Page: 1,
Size: 1000,
ServerId: []int64{data.ServerId},
Tag: strings.Split(data.Tags, ","),
Search: "",
Protocol: data.Protocol,
})
}
@@ -1,35 +0,0 @@
package server
import (
"context"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
)
type DeleteRuleGroupLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// Delete rule group
func NewDeleteRuleGroupLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeleteRuleGroupLogic {
return &DeleteRuleGroupLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *DeleteRuleGroupLogic) DeleteRuleGroup(req *types.DeleteRuleGroupRequest) error {
err := l.svcCtx.ServerModel.DeleteRuleGroup(l.ctx, req.Id)
if err != nil {
l.Errorw("[DeleteRuleGroup] Delete Database Error: ", logger.Field("error", err.Error()))
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseDeletedError), "delete server rule group error: %v", err)
}
return nil
}
@@ -0,0 +1,41 @@
package server
import (
"context"
"github.com/perfect-panel/server/internal/model/node"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
)
type DeleteServerLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// NewDeleteServerLogic Delete Server
func NewDeleteServerLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeleteServerLogic {
return &DeleteServerLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *DeleteServerLogic) DeleteServer(req *types.DeleteServerRequest) error {
err := l.svcCtx.NodeModel.DeleteServer(l.ctx, req.Id)
if err != nil {
l.Errorw("[DeleteServer] Delete Server Error: ", logger.Field("error", err.Error()))
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseDeletedError), "[DeleteServer] Delete Server Error")
}
return l.svcCtx.NodeModel.ClearNodeCache(l.ctx, &node.FilterNodeParams{
Page: 1,
Size: 1000,
ServerId: []int64{req.Id},
Search: "",
})
}
@@ -0,0 +1,64 @@
package server
import (
"context"
"strings"
"github.com/perfect-panel/server/internal/model/node"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/tool"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
)
type FilterNodeListLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// NewFilterNodeListLogic Filter Node List
func NewFilterNodeListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *FilterNodeListLogic {
return &FilterNodeListLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *FilterNodeListLogic) FilterNodeList(req *types.FilterNodeListRequest) (resp *types.FilterNodeListResponse, err error) {
total, data, err := l.svcCtx.NodeModel.FilterNodeList(l.ctx, &node.FilterNodeParams{
Page: req.Page,
Size: req.Size,
Search: req.Search,
})
if err != nil {
l.Errorw("[FilterNodeList] Query Database Error: ", logger.Field("error", err.Error()))
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "[FilterNodeList] Query Database Error")
}
list := make([]types.Node, 0)
for _, datum := range data {
list = append(list, types.Node{
Id: datum.Id,
Name: datum.Name,
Tags: tool.RemoveDuplicateElements(strings.Split(datum.Tags, ",")...),
Port: datum.Port,
Address: datum.Address,
ServerId: datum.ServerId,
Protocol: datum.Protocol,
Enabled: datum.Enabled,
Sort: datum.Sort,
CreatedAt: datum.CreatedAt.UnixMilli(),
UpdatedAt: datum.UpdatedAt.UnixMilli(),
})
}
return &types.FilterNodeListResponse{
List: list,
Total: total,
}, nil
}
@@ -0,0 +1,164 @@
package server
import (
"context"
"time"
"github.com/perfect-panel/server/internal/model/node"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/tool"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
"github.com/redis/go-redis/v9"
"gorm.io/gorm"
)
type FilterServerListLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// NewFilterServerListLogic Filter Server List
func NewFilterServerListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *FilterServerListLogic {
return &FilterServerListLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *FilterServerListLogic) FilterServerList(req *types.FilterServerListRequest) (resp *types.FilterServerListResponse, err error) {
total, data, err := l.svcCtx.NodeModel.FilterServerList(l.ctx, &node.FilterParams{
Page: req.Page,
Size: req.Size,
Search: req.Search,
})
if err != nil {
l.Errorw("[FilterServerList] Query Database Error: ", logger.Field("error", err.Error()))
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "[FilterServerList] Query Database Error")
}
list := make([]types.Server, 0)
for _, datum := range data {
var server types.Server
tool.DeepCopy(&server, datum)
// handler protocols
var protocols []types.Protocol
dst, err := datum.UnmarshalProtocols()
if err != nil {
l.Errorf("[FilterServerList] UnmarshalProtocols Error: %s", err.Error())
continue
}
tool.DeepCopy(&protocols, dst)
server.Protocols = protocols
nodeStatus, err := l.svcCtx.NodeModel.StatusCache(l.ctx, datum.Id)
if err != nil {
if !errors.Is(err, redis.Nil) {
l.Errorw("[handlerServerStatus] GetNodeStatus Error: ", logger.Field("error", err.Error()), logger.Field("node_id", datum.Id))
}
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "GetNodeStatus Error")
}
server.Status = types.ServerStatus{
Mem: nodeStatus.Mem,
Cpu: nodeStatus.Cpu,
Disk: nodeStatus.Disk,
Online: l.handlerServerStatus(datum.Id, protocols),
Status: l.handlerServerStaus(datum.LastReportedAt),
}
list = append(list, server)
}
return &types.FilterServerListResponse{
List: list,
Total: total,
}, nil
}
func (l *FilterServerListLogic) handlerServerStatus(id int64, protocols []types.Protocol) []types.ServerOnlineUser {
result := make([]types.ServerOnlineUser, 0)
for _, protocol := range protocols {
// query online user
data, err := l.svcCtx.NodeModel.OnlineUserSubscribe(l.ctx, id, protocol.Type)
if err != nil {
if !errors.Is(err, redis.Nil) {
l.Errorw("[handlerServerStatus] OnlineUserSubscribe Error: ", logger.Field("error", err.Error()), logger.Field("node_id", id), logger.Field("protocol", protocol.Type))
}
continue
}
if len(data) > 0 {
for sub, online := range data {
var ips []types.ServerOnlineIP
for _, ip := range online {
ips = append(ips, types.ServerOnlineIP{
IP: ip,
Protocol: protocol.Type,
})
}
result = append(result, types.ServerOnlineUser{
IP: ips,
SubscribeId: sub,
})
}
}
}
// merge same subscribe
var mapResult = make(map[int64]types.ServerOnlineUser)
for _, item := range result {
if exist, ok := mapResult[item.SubscribeId]; ok {
// merge
exist.Traffic += item.Traffic
exist.IP = append(exist.IP, item.IP...)
mapResult[item.SubscribeId] = exist
} else {
// get subscribe info
info, err := l.svcCtx.UserModel.FindOneUserSubscribe(l.ctx, item.SubscribeId)
if err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
l.Errorw("[handlerServerStatus] FindOneSubscribe Error: ", logger.Field("error", err.Error()), logger.Field("subscribe_id", item.SubscribeId))
}
continue
}
data := types.ServerOnlineUser{
IP: item.IP,
UserId: info.UserId,
Subscribe: "",
SubscribeId: item.SubscribeId,
Traffic: info.Download + info.Upload,
ExpiredAt: info.ExpireTime.UnixMilli(),
}
if info.Subscribe != nil {
data.Subscribe = info.Subscribe.Name
}
// add new
mapResult[item.SubscribeId] = data
}
}
// convert map to slice
result = make([]types.ServerOnlineUser, 0, len(mapResult))
for _, item := range mapResult {
result = append(result, item)
}
return result
}
func (l *FilterServerListLogic) handlerServerStaus(last *time.Time) string {
if last == nil {
return "offline"
}
if time.Since(*last) > time.Minute*5 {
return "offline"
}
if time.Since(*last) > time.Minute*3 {
return "warning"
}
return "online"
}
@@ -1,43 +0,0 @@
package server
import (
"context"
"encoding/json"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/tool"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
)
type GetNodeDetailLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetNodeDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetNodeDetailLogic {
return &GetNodeDetailLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetNodeDetailLogic) GetNodeDetail(req *types.GetDetailRequest) (resp *types.Server, err error) {
detail, err := l.svcCtx.ServerModel.FindOne(l.ctx, req.Id)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "get server detail error: %v", err.Error())
}
resp = &types.Server{}
tool.DeepCopy(resp, detail)
var cfg map[string]interface{}
err = json.Unmarshal([]byte(detail.Config), &cfg)
if err != nil {
cfg = make(map[string]interface{})
}
resp.Config = cfg
return
}
@@ -1,39 +0,0 @@
package server
import (
"context"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/tool"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
)
type GetNodeGroupListLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetNodeGroupListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetNodeGroupListLogic {
return &GetNodeGroupListLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetNodeGroupListLogic) GetNodeGroupList() (resp *types.GetNodeGroupListResponse, err error) {
nodeGroupList, err := l.svcCtx.ServerModel.QueryAllGroup(l.ctx)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), err.Error())
}
nodeGroups := make([]types.ServerGroup, 0)
tool.DeepCopy(&nodeGroups, nodeGroupList)
return &types.GetNodeGroupListResponse{
Total: int64(len(nodeGroups)),
List: nodeGroups,
}, nil
}
@@ -1,104 +0,0 @@
package server
import (
"context"
"encoding/json"
"strings"
"github.com/perfect-panel/server/internal/model/server"
"github.com/redis/go-redis/v9"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/tool"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
)
type GetNodeListLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetNodeListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetNodeListLogic {
return &GetNodeListLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetNodeListLogic) GetNodeList(req *types.GetNodeServerListRequest) (resp *types.GetNodeServerListResponse, err error) {
tags := make([]string, 0)
if req.Tags != "" {
tags = strings.Split(req.Tags, ",")
}
total, list, err := l.svcCtx.ServerModel.FindServerListByFilter(l.ctx, &server.ServerFilter{
Page: req.Page,
Size: req.Size,
Search: req.Search,
Tags: tags,
Group: req.GroupId,
})
if err != nil {
l.Errorw("[GetNodeList] Query Database Error: ", logger.Field("error", err.Error()))
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), err.Error())
}
nodes := make([]types.Server, 0)
for _, v := range list {
node := types.Server{}
tool.DeepCopy(&node, v)
// default relay mode
if node.RelayMode == "" {
node.RelayMode = "none"
}
if len(v.Tags) > 0 {
if strings.Contains(v.Tags, ",") {
node.Tags = strings.Split(v.Tags, ",")
} else {
node.Tags = []string{v.Tags}
}
}
// parse config
var cfg map[string]interface{}
err = json.Unmarshal([]byte(v.Config), &cfg)
if err != nil {
cfg = make(map[string]interface{})
}
node.Config = cfg
relayNode := make([]types.NodeRelay, 0)
err = json.Unmarshal([]byte(v.RelayNode), &relayNode)
if err != nil {
l.Errorw("[GetNodeList] Unmarshal RelayNode Error: ", logger.Field("error", err.Error()), logger.Field("relayNode", v.RelayNode))
}
node.RelayNode = relayNode
var status types.NodeStatus
nodeStatus, err := l.svcCtx.NodeCache.GetNodeStatus(l.ctx, v.Id)
if err != nil {
// redis nil is not a Error
if !errors.Is(err, redis.Nil) {
l.Errorw("[GetNodeList] Get Node Status Error: ", logger.Field("error", err.Error()))
}
} else {
onlineUser, err := l.svcCtx.NodeCache.GetNodeOnlineUser(l.ctx, v.Id)
if err != nil {
l.Errorw("[GetNodeList] Get Node Online User Error: ", logger.Field("error", err.Error()))
} else {
status.Online = onlineUser
}
status.Cpu = nodeStatus.Cpu
status.Mem = nodeStatus.Mem
status.Disk = nodeStatus.Disk
status.UpdatedAt = nodeStatus.UpdatedAt
}
node.Status = &status
nodes = append(nodes, node)
}
return &types.GetNodeServerListResponse{
Total: total,
List: nodes,
}, nil
}
@@ -1,31 +0,0 @@
package server
import (
"context"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/tool"
)
type GetNodeTagListLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// Get node tag list
func NewGetNodeTagListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetNodeTagListLogic {
return &GetNodeTagListLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetNodeTagListLogic) GetNodeTagList() (resp *types.GetNodeTagListResponse, err error) {
tags, err := l.svcCtx.ServerModel.FindServerTags(l.ctx)
return &types.GetNodeTagListResponse{
Tags: tool.RemoveDuplicateElements(tags...),
}, nil
}
@@ -1,54 +0,0 @@
package server
import (
"context"
"strings"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
)
type GetRuleGroupListLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// Get rule group list
func NewGetRuleGroupListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetRuleGroupListLogic {
return &GetRuleGroupListLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetRuleGroupListLogic) GetRuleGroupList() (resp *types.GetRuleGroupResponse, err error) {
nodeRuleGroupList, err := l.svcCtx.ServerModel.QueryAllRuleGroup(l.ctx)
if err != nil {
l.Errorw("[GetRuleGroupList] Query Database Error: ", logger.Field("error", err.Error()))
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), err.Error())
}
nodeRuleGroups := make([]types.ServerRuleGroup, len(nodeRuleGroupList))
for i, v := range nodeRuleGroupList {
nodeRuleGroups[i] = types.ServerRuleGroup{
Id: v.Id,
Icon: v.Icon,
Name: v.Name,
Type: v.Type,
Tags: strings.Split(v.Tags, ","),
Rules: v.Rules,
Enable: v.Enable,
Default: v.Default,
CreatedAt: v.CreatedAt.UnixMilli(),
UpdatedAt: v.UpdatedAt.UnixMilli(),
}
}
return &types.GetRuleGroupResponse{
Total: int64(len(nodeRuleGroups)),
List: nodeRuleGroups,
}, nil
}
@@ -0,0 +1,49 @@
package server
import (
"context"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/tool"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
)
type GetServerProtocolsLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// Get Server Protocols
func NewGetServerProtocolsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetServerProtocolsLogic {
return &GetServerProtocolsLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetServerProtocolsLogic) GetServerProtocols(req *types.GetServerProtocolsRequest) (resp *types.GetServerProtocolsResponse, err error) {
// find server
data, err := l.svcCtx.NodeModel.FindOneServer(l.ctx, req.Id)
if err != nil {
l.Errorf("[GetServerProtocols] FindOneServer Error: %s", err.Error())
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "[GetServerProtocols] FindOneServer Error: %s", err.Error())
}
// handler protocols
var protocols []types.Protocol
dst, err := data.UnmarshalProtocols()
if err != nil {
l.Errorf("[FilterServerList] UnmarshalProtocols Error: %s", err.Error())
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "[FilterServerList] UnmarshalProtocols Error: %s", err.Error())
}
tool.DeepCopy(&protocols, dst)
return &types.GetServerProtocolsResponse{
Protocols: protocols,
}, nil
}
@@ -0,0 +1,52 @@
package server
import (
"context"
"github.com/perfect-panel/server/internal/model/node"
"github.com/perfect-panel/server/internal/model/server"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
)
type HasMigrateSeverNodeLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// NewHasMigrateSeverNodeLogic Check if there is any server or node to migrate
func NewHasMigrateSeverNodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *HasMigrateSeverNodeLogic {
return &HasMigrateSeverNodeLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *HasMigrateSeverNodeLogic) HasMigrateSeverNode() (resp *types.HasMigrateSeverNodeResponse, err error) {
var oldCount, newCount int64
query := l.svcCtx.DB.WithContext(l.ctx)
err = query.Model(&server.Server{}).Count(&oldCount).Error
if err != nil {
l.Errorw("[HasMigrateSeverNode] Query Old Server Count Error: ", logger.Field("error", err.Error()))
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "[HasMigrateSeverNode] Query Old Server Count Error")
}
err = query.Model(&node.Server{}).Count(&newCount).Error
if err != nil {
l.Errorw("[HasMigrateSeverNode] Query New Server Count Error: ", logger.Field("error", err.Error()))
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "[HasMigrateSeverNode] Query New Server Count Error")
}
var shouldMigrate bool
if oldCount != 0 && newCount == 0 {
shouldMigrate = true
}
return &types.HasMigrateSeverNodeResponse{
HasMigrate: shouldMigrate,
}, nil
}
@@ -0,0 +1,338 @@
package server
import (
"context"
"encoding/json"
"fmt"
"github.com/perfect-panel/server/internal/model/node"
"github.com/perfect-panel/server/internal/model/server"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
)
type MigrateServerNodeLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// NewMigrateServerNodeLogic Migrate server and node data to new database
func NewMigrateServerNodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *MigrateServerNodeLogic {
return &MigrateServerNodeLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *MigrateServerNodeLogic) MigrateServerNode() (resp *types.MigrateServerNodeResponse, err error) {
tx := l.svcCtx.DB.WithContext(l.ctx).Begin()
var oldServers []*server.Server
var newServers []*node.Server
var newNodes []*node.Node
err = tx.Model(&server.Server{}).Find(&oldServers).Error
if err != nil {
l.Errorw("[MigrateServerNode] Query Old Server List Error: ", logger.Field("error", err.Error()))
return &types.MigrateServerNodeResponse{
Succee: 0,
Fail: 0,
Message: fmt.Sprintf("Query Old Server List Error: %s", err.Error()),
}, nil
}
for _, oldServer := range oldServers {
data, err := l.adapterServer(oldServer)
if err != nil {
l.Errorw("[MigrateServerNode] Adapter Server Error: ", logger.Field("error", err.Error()))
if resp == nil {
resp = &types.MigrateServerNodeResponse{}
}
resp.Fail++
if resp.Message == "" {
resp.Message = fmt.Sprintf("Adapter Server Error: %s", err.Error())
} else {
resp.Message = fmt.Sprintf("%s; Adapter Server Error: %s", resp.Message, err.Error())
}
continue
}
newServers = append(newServers, data)
newNode, err := l.adapterNode(oldServer)
if err != nil {
l.Errorw("[MigrateServerNode] Adapter Node Error: ", logger.Field("error", err.Error()))
if resp == nil {
resp = &types.MigrateServerNodeResponse{}
}
resp.Fail++
if resp.Message == "" {
resp.Message = fmt.Sprintf("Adapter Node Error: %s", err.Error())
} else {
resp.Message = fmt.Sprintf("%s; Adapter Node Error: %s", resp.Message, err.Error())
}
continue
}
for _, item := range newNode {
if item.Port == 0 {
protocols, _ := data.UnmarshalProtocols()
if len(protocols) > 0 {
item.Port = protocols[0].Port
}
}
newNodes = append(newNodes, item)
}
}
if len(newServers) > 0 {
err = tx.Model(&node.Server{}).CreateInBatches(newServers, 20).Error
if err != nil {
tx.Rollback()
l.Errorw("[MigrateServerNode] Insert New Server List Error: ", logger.Field("error", err.Error()))
return &types.MigrateServerNodeResponse{
Succee: 0,
Fail: uint64(len(newServers)),
Message: fmt.Sprintf("Insert New Server List Error: %s", err.Error()),
}, nil
}
}
if len(newNodes) > 0 {
err = tx.Model(&node.Node{}).CreateInBatches(newNodes, 20).Error
if err != nil {
tx.Rollback()
l.Errorw("[MigrateServerNode] Insert New Node List Error: ", logger.Field("error", err.Error()))
return &types.MigrateServerNodeResponse{
Succee: uint64(len(newServers)),
Fail: uint64(len(newNodes)),
Message: fmt.Sprintf("Insert New Node List Error: %s", err.Error()),
}, nil
}
}
tx.Commit()
return &types.MigrateServerNodeResponse{
Succee: uint64(len(newServers)),
Fail: 0,
Message: fmt.Sprintf("Migrate Success: %d servers and %d nodes", len(newServers), len(newNodes)),
}, nil
}
func (l *MigrateServerNodeLogic) adapterServer(info *server.Server) (*node.Server, error) {
result := &node.Server{
Id: info.Id,
Name: info.Name,
Country: info.Country,
City: info.City,
//Ratio: info.TrafficRatio,
Address: info.ServerAddr,
Sort: int(info.Sort),
Protocols: "",
}
var protocols []node.Protocol
switch info.Protocol {
case ShadowSocks:
var src server.Shadowsocks
err := json.Unmarshal([]byte(info.Config), &src)
if err != nil {
return nil, err
}
protocols = append(protocols, node.Protocol{
Type: "shadowsocks",
Cipher: src.Method,
Port: uint16(src.Port),
ServerKey: src.ServerKey,
Ratio: float64(info.TrafficRatio),
})
case Vmess:
var src server.Vmess
err := json.Unmarshal([]byte(info.Config), &src)
if err != nil {
return nil, err
}
protocol := node.Protocol{
Type: "vmess",
Port: uint16(src.Port),
Security: src.Security,
SNI: src.SecurityConfig.SNI,
AllowInsecure: src.SecurityConfig.AllowInsecure,
Fingerprint: src.SecurityConfig.Fingerprint,
RealityServerAddr: src.SecurityConfig.RealityServerAddr,
RealityServerPort: src.SecurityConfig.RealityServerPort,
RealityPrivateKey: src.SecurityConfig.RealityPrivateKey,
RealityPublicKey: src.SecurityConfig.RealityPublicKey,
RealityShortId: src.SecurityConfig.RealityShortId,
Transport: src.Transport,
Host: src.TransportConfig.Host,
Path: src.TransportConfig.Path,
ServiceName: src.TransportConfig.ServiceName,
Flow: src.Flow,
Ratio: float64(info.TrafficRatio),
}
protocols = append(protocols, protocol)
protocols = append(protocols, protocol)
case Vless:
var src server.Vless
err := json.Unmarshal([]byte(info.Config), &src)
if err != nil {
return nil, err
}
protocol := node.Protocol{
Type: "vless",
Port: uint16(src.Port),
Security: src.Security,
SNI: src.SecurityConfig.SNI,
AllowInsecure: src.SecurityConfig.AllowInsecure,
Fingerprint: src.SecurityConfig.Fingerprint,
RealityServerAddr: src.SecurityConfig.RealityServerAddr,
RealityServerPort: src.SecurityConfig.RealityServerPort,
RealityPrivateKey: src.SecurityConfig.RealityPrivateKey,
RealityPublicKey: src.SecurityConfig.RealityPublicKey,
RealityShortId: src.SecurityConfig.RealityShortId,
Transport: src.Transport,
Host: src.TransportConfig.Host,
Path: src.TransportConfig.Path,
ServiceName: src.TransportConfig.ServiceName,
Flow: src.Flow,
Ratio: float64(info.TrafficRatio),
}
protocols = append(protocols, protocol)
case Trojan:
var src server.Trojan
err := json.Unmarshal([]byte(info.Config), &src)
if err != nil {
return nil, err
}
protocol := node.Protocol{
Type: "trojan",
Port: uint16(src.Port),
Security: src.Security,
SNI: src.SecurityConfig.SNI,
AllowInsecure: src.SecurityConfig.AllowInsecure,
Fingerprint: src.SecurityConfig.Fingerprint,
RealityServerAddr: src.SecurityConfig.RealityServerAddr,
RealityServerPort: src.SecurityConfig.RealityServerPort,
RealityPrivateKey: src.SecurityConfig.RealityPrivateKey,
RealityPublicKey: src.SecurityConfig.RealityPublicKey,
RealityShortId: src.SecurityConfig.RealityShortId,
Transport: src.Transport,
Host: src.TransportConfig.Host,
Path: src.TransportConfig.Path,
ServiceName: src.TransportConfig.ServiceName,
Flow: src.Flow,
Ratio: float64(info.TrafficRatio),
}
protocols = append(protocols, protocol)
case Hysteria2:
var src server.Hysteria2
err := json.Unmarshal([]byte(info.Config), &src)
if err != nil {
return nil, err
}
protocol := node.Protocol{
Type: "hysteria",
Port: uint16(src.Port),
HopPorts: src.HopPorts,
HopInterval: src.HopInterval,
ObfsPassword: src.ObfsPassword,
SNI: src.SecurityConfig.SNI,
AllowInsecure: src.SecurityConfig.AllowInsecure,
Fingerprint: src.SecurityConfig.Fingerprint,
RealityServerAddr: src.SecurityConfig.RealityServerAddr,
RealityServerPort: src.SecurityConfig.RealityServerPort,
RealityPrivateKey: src.SecurityConfig.RealityPrivateKey,
RealityPublicKey: src.SecurityConfig.RealityPublicKey,
RealityShortId: src.SecurityConfig.RealityShortId,
Ratio: float64(info.TrafficRatio),
}
protocols = append(protocols, protocol)
case Tuic:
var src server.Tuic
err := json.Unmarshal([]byte(info.Config), &src)
if err != nil {
return nil, err
}
protocol := node.Protocol{
Type: "tuic",
Port: uint16(src.Port),
DisableSNI: src.DisableSNI,
ReduceRtt: src.ReduceRtt,
UDPRelayMode: src.UDPRelayMode,
CongestionController: src.CongestionController,
SNI: src.SecurityConfig.SNI,
AllowInsecure: src.SecurityConfig.AllowInsecure,
Fingerprint: src.SecurityConfig.Fingerprint,
RealityServerAddr: src.SecurityConfig.RealityServerAddr,
RealityServerPort: src.SecurityConfig.RealityServerPort,
RealityPrivateKey: src.SecurityConfig.RealityPrivateKey,
RealityPublicKey: src.SecurityConfig.RealityPublicKey,
RealityShortId: src.SecurityConfig.RealityShortId,
Ratio: float64(info.TrafficRatio),
}
protocols = append(protocols, protocol)
case AnyTLS:
var src server.AnyTLS
err := json.Unmarshal([]byte(info.Config), &src)
if err != nil {
return nil, err
}
protocol := node.Protocol{
Type: "anytls",
Port: uint16(src.Port),
SNI: src.SecurityConfig.SNI,
AllowInsecure: src.SecurityConfig.AllowInsecure,
Fingerprint: src.SecurityConfig.Fingerprint,
RealityServerAddr: src.SecurityConfig.RealityServerAddr,
RealityServerPort: src.SecurityConfig.RealityServerPort,
RealityPrivateKey: src.SecurityConfig.RealityPrivateKey,
RealityPublicKey: src.SecurityConfig.RealityPublicKey,
RealityShortId: src.SecurityConfig.RealityShortId,
Ratio: float64(info.TrafficRatio),
}
protocols = append(protocols, protocol)
}
if len(protocols) > 0 {
err := result.MarshalProtocols(protocols)
if err != nil {
return nil, err
}
}
return result, nil
}
func (l *MigrateServerNodeLogic) adapterNode(info *server.Server) ([]*node.Node, error) {
var nodes []*node.Node
enable := true
switch info.RelayMode {
case server.RelayModeNone:
nodes = append(nodes, &node.Node{
Name: info.Name,
Tags: "",
Port: 0,
Address: info.ServerAddr,
ServerId: info.Id,
Protocol: info.Protocol,
Enabled: &enable,
})
default:
var relays []server.NodeRelay
err := json.Unmarshal([]byte(info.RelayNode), &relays)
if err != nil {
return nil, err
}
for _, relay := range relays {
nodes = append(nodes, &node.Node{
Name: relay.Prefix + info.Name,
Tags: "",
Port: uint16(relay.Port),
Address: relay.Host,
ServerId: info.Id,
Protocol: info.Protocol,
Enabled: &enable,
})
}
}
return nodes, nil
}
@@ -0,0 +1,46 @@
package server
import (
"context"
"strings"
"github.com/perfect-panel/server/internal/model/node"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/tool"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
)
type QueryNodeTagLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// NewQueryNodeTagLogic Query all node tags
func NewQueryNodeTagLogic(ctx context.Context, svcCtx *svc.ServiceContext) *QueryNodeTagLogic {
return &QueryNodeTagLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *QueryNodeTagLogic) QueryNodeTag() (resp *types.QueryNodeTagResponse, err error) {
var nodes []*node.Node
if err = l.svcCtx.DB.WithContext(l.ctx).Model(&node.Node{}).Find(&nodes).Error; err != nil {
l.Errorw("[QueryNodeTag] Query Database Error: ", logger.Field("error", err.Error()))
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "[QueryNodeTag] Query Database Error")
}
var tags []string
for _, item := range nodes {
tags = append(tags, strings.Split(item.Tags, ",")...)
}
return &types.QueryNodeTagResponse{
Tags: tool.RemoveDuplicateElements(tags...),
}, nil
}
@@ -3,36 +3,35 @@ package server
import (
"context"
"github.com/perfect-panel/server/internal/model/server"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
"gorm.io/gorm"
"github.com/perfect-panel/server/internal/model/node"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
"gorm.io/gorm"
)
type NodeSortLogic struct {
type ResetSortWithNodeLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// Node sort
func NewNodeSortLogic(ctx context.Context, svcCtx *svc.ServiceContext) *NodeSortLogic {
return &NodeSortLogic{
// NewResetSortWithNodeLogic Reset node sort
func NewResetSortWithNodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ResetSortWithNodeLogic {
return &ResetSortWithNodeLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *NodeSortLogic) NodeSort(req *types.NodeSortRequest) error {
err := l.svcCtx.ServerModel.Transaction(l.ctx, func(db *gorm.DB) error {
func (l *ResetSortWithNodeLogic) ResetSortWithNode(req *types.ResetSortRequest) error {
err := l.svcCtx.NodeModel.Transaction(l.ctx, func(db *gorm.DB) error {
// find all servers id
var existingIDs []int64
db.Model(&server.Server{}).Select("id").Find(&existingIDs)
db.Model(&node.Node{}).Select("id").Find(&existingIDs)
// check if the id is valid
validIDMap := make(map[int64]bool)
for _, id := range existingIDs {
@@ -46,12 +45,12 @@ func (l *NodeSortLogic) NodeSort(req *types.NodeSortRequest) error {
}
}
// query all servers
var servers []*server.Server
db.Model(&server.Server{}).Order("sort ASC").Find(&servers)
var servers []*node.Node
db.Model(&node.Node{}).Order("sort ASC").Find(&servers)
// create a map of the current sort
currentSortMap := make(map[int64]int64)
for _, item := range servers {
currentSortMap[item.Id] = item.Sort
currentSortMap[item.Id] = int64(item.Sort)
}
// new sort map
@@ -67,12 +66,12 @@ func (l *NodeSortLogic) NodeSort(req *types.NodeSortRequest) error {
}
}
for _, item := range itemsToUpdate {
s, err := l.svcCtx.ServerModel.FindOne(l.ctx, item.Id)
s, err := l.svcCtx.NodeModel.FindOneNode(l.ctx, item.Id)
if err != nil {
return err
}
s.Sort = item.Sort
if err := l.svcCtx.ServerModel.Update(l.ctx, s, db); err != nil {
s.Sort = int(item.Sort)
if err = l.svcCtx.NodeModel.UpdateNode(l.ctx, s, db); err != nil {
l.Errorw("[NodeSort] Update Database Error: ", logger.Field("error", err.Error()), logger.Field("id", item.Id), logger.Field("sort", item.Sort))
return err
}
@@ -0,0 +1,86 @@
package server
import (
"context"
"github.com/perfect-panel/server/internal/model/node"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
"gorm.io/gorm"
)
type ResetSortWithServerLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// NewResetSortWithServerLogic Reset server sort
func NewResetSortWithServerLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ResetSortWithServerLogic {
return &ResetSortWithServerLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *ResetSortWithServerLogic) ResetSortWithServer(req *types.ResetSortRequest) error {
err := l.svcCtx.NodeModel.Transaction(l.ctx, func(db *gorm.DB) error {
// find all servers id
var existingIDs []int64
db.Model(&node.Server{}).Select("id").Find(&existingIDs)
// check if the id is valid
validIDMap := make(map[int64]bool)
for _, id := range existingIDs {
validIDMap[id] = true
}
// check if the sort is valid
var validItems []types.SortItem
for _, item := range req.Sort {
if validIDMap[item.Id] {
validItems = append(validItems, item)
}
}
// query all servers
var servers []*node.Server
db.Model(&node.Server{}).Order("sort ASC").Find(&servers)
// create a map of the current sort
currentSortMap := make(map[int64]int64)
for _, item := range servers {
currentSortMap[item.Id] = int64(item.Sort)
}
// new sort map
newSortMap := make(map[int64]int64)
for _, item := range validItems {
newSortMap[item.Id] = item.Sort
}
var itemsToUpdate []types.SortItem
for _, item := range validItems {
if oldSort, exists := currentSortMap[item.Id]; exists && oldSort != item.Sort {
itemsToUpdate = append(itemsToUpdate, item)
}
}
for _, item := range itemsToUpdate {
s, err := l.svcCtx.NodeModel.FindOneServer(l.ctx, item.Id)
if err != nil {
return err
}
s.Sort = int(item.Sort)
if err = l.svcCtx.NodeModel.UpdateServer(l.ctx, s, db); err != nil {
l.Errorw("[NodeSort] Update Database Error: ", logger.Field("error", err.Error()), logger.Field("id", item.Id), logger.Field("sort", item.Sort))
return err
}
}
return nil
})
if err != nil {
l.Errorw("[NodeSort] Update Database Error: ", logger.Field("error", err.Error()))
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), err.Error())
}
return nil
}
@@ -0,0 +1,51 @@
package server
import (
"context"
"strings"
"github.com/perfect-panel/server/internal/model/node"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
)
type ToggleNodeStatusLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// NewToggleNodeStatusLogic Toggle Node Status
func NewToggleNodeStatusLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ToggleNodeStatusLogic {
return &ToggleNodeStatusLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *ToggleNodeStatusLogic) ToggleNodeStatus(req *types.ToggleNodeStatusRequest) error {
data, err := l.svcCtx.NodeModel.FindOneNode(l.ctx, req.Id)
if err != nil {
l.Errorw("[ToggleNodeStatus] Query Database Error: ", logger.Field("error", err.Error()))
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "[ToggleNodeStatus] Query Database Error")
}
data.Enabled = req.Enable
err = l.svcCtx.NodeModel.UpdateNode(l.ctx, data)
if err != nil {
l.Errorw("[ToggleNodeStatus] Update Database Error: ", logger.Field("error", err.Error()))
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "[ToggleNodeStatus] Update Database Error")
}
return l.svcCtx.NodeModel.ClearNodeCache(l.ctx, &node.FilterNodeParams{
Page: 1,
Size: 1000,
ServerId: []int64{data.ServerId},
Tag: strings.Split(data.Tags, ","),
Search: "",
})
}
@@ -1,40 +0,0 @@
package server
import (
"context"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
)
type UpdateNodeGroupLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewUpdateNodeGroupLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateNodeGroupLogic {
return &UpdateNodeGroupLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *UpdateNodeGroupLogic) UpdateNodeGroup(req *types.UpdateNodeGroupRequest) error {
// check server group exist
nodeGroup, err := l.svcCtx.ServerModel.FindOneGroup(l.ctx, req.Id)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), err.Error())
}
nodeGroup.Name = req.Name
nodeGroup.Description = req.Description
err = l.svcCtx.ServerModel.UpdateGroup(l.ctx, nodeGroup)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), err.Error())
}
return nil
}
+20 -104
View File
@@ -2,13 +2,8 @@ package server
import (
"context"
"encoding/json"
"strings"
"github.com/hibiken/asynq"
"github.com/perfect-panel/server/pkg/device"
queue "github.com/perfect-panel/server/queue/types"
"github.com/perfect-panel/server/internal/model/node"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
@@ -23,6 +18,7 @@ type UpdateNodeLogic struct {
svcCtx *svc.ServiceContext
}
// NewUpdateNodeLogic Update Node
func NewUpdateNodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateNodeLogic {
return &UpdateNodeLogic{
Logger: logger.WithContext(ctx),
@@ -32,107 +28,27 @@ func NewUpdateNodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Update
}
func (l *UpdateNodeLogic) UpdateNode(req *types.UpdateNodeRequest) error {
// Check server exist
nodeInfo, err := l.svcCtx.ServerModel.FindOne(l.ctx, req.Id)
data, err := l.svcCtx.NodeModel.FindOneNode(l.ctx, req.Id)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find server error: %v", err)
l.Errorw("[UpdateNode] Query Database Error: ", logger.Field("error", err.Error()))
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "[UpdateNode] Query Database Error")
}
tool.DeepCopy(nodeInfo, req, tool.CopyWithIgnoreEmpty(false))
config, err := json.Marshal(req.Config)
if err != nil {
return err
}
nodeInfo.Config = string(config)
nodeRelay, err := json.Marshal(req.RelayNode)
if err != nil {
l.Errorw("[UpdateNode] Marshal RelayNode Error: ", logger.Field("error", err.Error()))
return err
}
// 处理Tags字段
switch {
case len(req.Tags) > 0:
// 有Tags,进行连接
nodeInfo.Tags = strings.Join(req.Tags, ",")
default:
// 空数组,清空Tags
nodeInfo.Tags = ""
}
nodeInfo.City = req.City
nodeInfo.Country = req.Country
nodeInfo.RelayNode = string(nodeRelay)
if req.Protocol == "vless" {
var cfg types.Vless
if err := json.Unmarshal(config, &cfg); err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "json.Unmarshal error: %v", err.Error())
}
if cfg.Security == "reality" && cfg.SecurityConfig.RealityPublicKey == "" {
public, private, err := tool.Curve25519Genkey(false, "")
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "generate curve25519 key error")
}
cfg.SecurityConfig.RealityPublicKey = public
cfg.SecurityConfig.RealityPrivateKey = private
cfg.SecurityConfig.RealityShortId = tool.GenerateShortID(private)
}
if cfg.SecurityConfig.RealityServerAddr == "" {
cfg.SecurityConfig.RealityServerAddr = cfg.SecurityConfig.SNI
}
if cfg.SecurityConfig.RealityServerPort == 0 {
cfg.SecurityConfig.RealityServerPort = 443
}
config, _ = json.Marshal(cfg)
nodeInfo.Config = string(config)
} else if req.Protocol == "shadowsocks" {
var cfg types.Shadowsocks
if err = json.Unmarshal(config, &cfg); err != nil {
l.Errorf("[CreateNode] Unmarshal Shadowsocks Config Error: %v", err.Error())
return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "json.Unmarshal error: %v", err.Error())
}
if strings.Contains(cfg.Method, "2022") {
var length int
switch cfg.Method {
case "2022-blake3-aes-128-gcm":
length = 16
default:
length = 32
}
if len(cfg.ServerKey) != length {
cfg.ServerKey = tool.GenerateCipher(cfg.ServerKey, length)
}
}
config, _ = json.Marshal(cfg)
nodeInfo.Config = string(config)
}
err = l.svcCtx.ServerModel.Update(l.ctx, nodeInfo)
data.Name = req.Name
data.Tags = tool.StringSliceToString(req.Tags)
data.ServerId = req.ServerId
data.Port = req.Port
data.Address = req.Address
data.Protocol = req.Protocol
data.Enabled = req.Enabled
err = l.svcCtx.NodeModel.UpdateNode(l.ctx, data)
if err != nil {
l.Errorw("[UpdateNode] Update Database Error: ", logger.Field("error", err.Error()))
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "create server error: %v", err)
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "[UpdateNode] Update Database Error")
}
if req.City == "" || req.Country == "" {
// Marshal the task payload
payload, err := json.Marshal(queue.GetNodeCountry{
Protocol: nodeInfo.Protocol,
ServerAddr: nodeInfo.ServerAddr,
})
if err != nil {
l.Errorw("[GetNodeCountry]: Marshal Error", logger.Field("error", err.Error()))
return errors.Wrap(xerr.NewErrCode(xerr.ERROR), "Failed to marshal task payload")
}
// Create a queue task
task := asynq.NewTask(queue.ForthwithGetCountry, payload)
// Enqueue the task
taskInfo, err := l.svcCtx.Queue.Enqueue(task)
if err != nil {
l.Errorw("[GetNodeCountry]: Enqueue Error", logger.Field("error", err.Error()), logger.Field("payload", string(payload)))
return errors.Wrap(xerr.NewErrCode(xerr.ERROR), "Failed to enqueue task")
}
l.Infow("[GetNodeCountry]: Enqueue Success", logger.Field("taskID", taskInfo.ID), logger.Field("payload", string(payload)))
}
l.svcCtx.DeviceManager.Broadcast(device.SubscribeUpdate)
return nil
return l.svcCtx.NodeModel.ClearNodeCache(l.ctx, &node.FilterNodeParams{
Page: 1,
Size: 1000,
ServerId: []int64{data.ServerId},
Search: "",
})
}
@@ -1,58 +0,0 @@
package server
import (
"context"
"strings"
"github.com/perfect-panel/server/pkg/tool"
"github.com/perfect-panel/server/internal/model/server"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
)
type UpdateRuleGroupLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// NewUpdateRuleGroupLogic Update rule group
func NewUpdateRuleGroupLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateRuleGroupLogic {
return &UpdateRuleGroupLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *UpdateRuleGroupLogic) UpdateRuleGroup(req *types.UpdateRuleGroupRequest) error {
rs, err := parseAndValidateRules(req.Rules, req.Name)
if err != nil {
return err
}
err = l.svcCtx.ServerModel.UpdateRuleGroup(l.ctx, &server.RuleGroup{
Id: req.Id,
Icon: req.Icon,
Type: req.Type,
Name: req.Name,
Tags: tool.StringSliceToString(req.Tags),
Rules: strings.Join(rs, "\n"),
Default: req.Default,
Enable: req.Enable,
})
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), err.Error())
}
if req.Default {
if err = l.svcCtx.ServerModel.SetDefaultRuleGroup(l.ctx, req.Id); err != nil {
l.Errorf("SetDefaultRuleGroup error: %v", err.Error())
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), err.Error())
}
}
return nil
}
@@ -0,0 +1,119 @@
package server
import (
"context"
"strings"
"github.com/perfect-panel/server/internal/model/node"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/ip"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/tool"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
)
type UpdateServerLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// NewUpdateServerLogic Update Server
func NewUpdateServerLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateServerLogic {
return &UpdateServerLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *UpdateServerLogic) UpdateServer(req *types.UpdateServerRequest) error {
data, err := l.svcCtx.NodeModel.FindOneServer(l.ctx, req.Id)
if err != nil {
l.Errorf("[UpdateServer] FindOneServer Error: %v", err.Error())
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find server error: %v", err.Error())
}
data.Name = req.Name
data.Country = req.Country
data.City = req.City
// only update address when it's different
if req.Address != data.Address {
// query server ip location
result, err := ip.GetRegionByIp(req.Address)
if err != nil {
l.Errorf("[UpdateServer] GetRegionByIp Error: %v", err.Error())
} else {
data.City = result.City
data.Country = result.Country
}
// update address
data.Address = req.Address
}
protocols := make([]node.Protocol, 0)
for _, item := range req.Protocols {
if item.Type == "" {
return errors.Wrapf(xerr.NewErrCodeMsg(xerr.InvalidParams, "protocols type is empty"), "protocols type is empty")
}
var protocol node.Protocol
tool.DeepCopy(&protocol, item)
// VLESS Reality Key Generation
if protocol.Type == "vless" {
if protocol.Security == "reality" {
if protocol.RealityPublicKey == "" {
public, private, err := tool.Curve25519Genkey(false, "")
if err != nil {
l.Errorf("[CreateServer] Generate Reality Key Error: %v", err.Error())
return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "generate reality key error: %v", err)
}
protocol.RealityPublicKey = public
protocol.RealityPrivateKey = private
protocol.RealityShortId = tool.GenerateShortID(private)
}
if protocol.RealityServerAddr == "" {
protocol.RealityServerAddr = protocol.SNI
}
if protocol.RealityServerPort == 0 {
protocol.RealityServerPort = 443
}
}
}
// ShadowSocks 2022 Key Generation
if protocol.Type == "shadowsocks" {
if strings.Contains(protocol.Cipher, "2022") {
var length int
switch protocol.Cipher {
case "2022-blake3-aes-128-gcm":
length = 16
default:
length = 32
}
if len(protocol.ServerKey) != length {
protocol.ServerKey = tool.GenerateCipher(protocol.ServerKey, length)
}
}
}
protocols = append(protocols, protocol)
}
err = data.MarshalProtocols(protocols)
if err != nil {
l.Errorf("[UpdateServer] Marshal Protocols Error: %v", err.Error())
return errors.Wrapf(xerr.NewErrCodeMsg(xerr.InvalidParams, "protocols marshal error"), "protocols marshal error: %v", err)
}
err = l.svcCtx.NodeModel.UpdateServer(l.ctx, data)
if err != nil {
l.Errorf("[UpdateServer] UpdateServer Error: %v", err.Error())
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update server error: %v", err.Error())
}
return l.svcCtx.NodeModel.ClearNodeCache(l.ctx, &node.FilterNodeParams{
Page: 1,
Size: 1000,
ServerId: []int64{req.Id},
Search: "",
})
}
@@ -37,6 +37,7 @@ func (l *CreateSubscribeLogic) CreateSubscribe(req *types.CreateSubscribeRequest
sub := &subscribe.Subscribe{
Id: 0,
Name: req.Name,
Language: req.Language,
Description: req.Description,
UnitPrice: req.UnitPrice,
UnitTime: req.UnitTime,
@@ -47,9 +48,8 @@ func (l *CreateSubscribeLogic) CreateSubscribe(req *types.CreateSubscribeRequest
SpeedLimit: req.SpeedLimit,
DeviceLimit: req.DeviceLimit,
Quota: req.Quota,
GroupId: req.GroupId,
ServerGroup: tool.Int64SliceToString(req.ServerGroup),
Server: tool.Int64SliceToString(req.Server),
Nodes: tool.Int64SliceToString(req.Nodes),
NodeTags: tool.StringSliceToString(req.NodeTags),
Show: req.Show,
Sell: req.Sell,
Sort: 0,
@@ -3,6 +3,7 @@ package subscribe
import (
"context"
"encoding/json"
"strings"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
@@ -41,7 +42,7 @@ func (l *GetSubscribeDetailsLogic) GetSubscribeDetails(req *types.GetSubscribeDe
l.Logger.Error("[GetSubscribeDetailsLogic] JSON unmarshal failed: ", logger.Field("error", err.Error()), logger.Field("discount", sub.Discount))
}
}
resp.Server = tool.StringToInt64Slice(sub.Server)
resp.ServerGroup = tool.StringToInt64Slice(sub.ServerGroup)
resp.Nodes = tool.StringToInt64Slice(sub.Nodes)
resp.NodeTags = strings.Split(sub.NodeTags, ",")
return resp, nil
}
@@ -3,7 +3,9 @@ package subscribe
import (
"context"
"encoding/json"
"strings"
"github.com/perfect-panel/server/internal/model/subscribe"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
@@ -28,7 +30,12 @@ func NewGetSubscribeListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *
}
func (l *GetSubscribeListLogic) GetSubscribeList(req *types.GetSubscribeListRequest) (resp *types.GetSubscribeListResponse, err error) {
total, list, err := l.svcCtx.SubscribeModel.QuerySubscribeListByPage(l.ctx, int(req.Page), int(req.Size), req.GroupId, req.Search)
total, list, err := l.svcCtx.SubscribeModel.FilterList(l.ctx, &subscribe.FilterParams{
Page: int(req.Page),
Size: int(req.Size),
Language: req.Language,
Search: req.Search,
})
if err != nil {
l.Logger.Error("[GetSubscribeListLogic] get subscribe list failed: ", logger.Field("error", err.Error()))
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "get subscribe list failed: %v", err.Error())
@@ -47,15 +54,8 @@ func (l *GetSubscribeListLogic) GetSubscribeList(req *types.GetSubscribeListRequ
l.Logger.Error("[GetSubscribeListLogic] JSON unmarshal failed: ", logger.Field("error", err.Error()), logger.Field("discount", item.Discount))
}
}
servers, err := l.svcCtx.ServerModel.FindServerListByGroupIds(l.ctx, sub.ServerGroup)
if err != nil {
l.Errorw("[QuerySubscribeListLogic] FindServerListByGroupIds error", logger.Field("error", err.Error()))
sub.ServerCount = 0
} else {
sub.ServerCount = int64(len(servers))
}
sub.Server = tool.StringToInt64Slice(item.Server)
sub.ServerGroup = tool.StringToInt64Slice(item.ServerGroup)
sub.Nodes = tool.StringToInt64Slice(item.Nodes)
sub.NodeTags = strings.Split(item.NodeTags, ",")
resultList = append(resultList, sub)
}
@@ -66,8 +66,8 @@ func (l *GetSubscribeListLogic) GetSubscribeList(req *types.GetSubscribeListRequ
}
for i, item := range resultList {
if subscribe, ok := subscribeMaps[item.Id]; ok {
resultList[i].Sold = subscribe
if sub, ok := subscribeMaps[item.Id]; ok {
resultList[i].Sold = sub
}
}
@@ -3,6 +3,7 @@ package subscribe
import (
"context"
"github.com/perfect-panel/server/internal/model/subscribe"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
"gorm.io/gorm"
@@ -40,7 +41,11 @@ func (l *SubscribeSortLogic) SubscribeSort(req *types.SubscribeSortRequest) erro
l.Logger.Error("[SubscribeSortLogic] query subscribe list by ids error: ", logger.Field("error", err.Error()))
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query subscribe list by ids error: %v", err.Error())
}
subs, err := l.svcCtx.SubscribeModel.QuerySubscribeListByIds(l.ctx, ids)
_, subs, err := l.svcCtx.SubscribeModel.FilterList(l.ctx, &subscribe.FilterParams{
Page: 1,
Size: 9999,
Ids: ids,
})
if err != nil {
l.Logger.Error("[SubscribeSortLogic] query subscribe list by ids error: ", logger.Field("error", err.Error()))
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query subscribe list by ids error: %v", err.Error())
@@ -45,6 +45,7 @@ func (l *UpdateSubscribeLogic) UpdateSubscribe(req *types.UpdateSubscribeRequest
sub := &subscribe.Subscribe{
Id: req.Id,
Name: req.Name,
Language: req.Language,
Description: req.Description,
UnitPrice: req.UnitPrice,
UnitTime: req.UnitTime,
@@ -55,9 +56,8 @@ func (l *UpdateSubscribeLogic) UpdateSubscribe(req *types.UpdateSubscribeRequest
SpeedLimit: req.SpeedLimit,
DeviceLimit: req.DeviceLimit,
Quota: req.Quota,
GroupId: req.GroupId,
ServerGroup: tool.Int64SliceToString(req.ServerGroup),
Server: tool.Int64SliceToString(req.Server),
Nodes: tool.Int64SliceToString(req.Nodes),
NodeTags: tool.StringSliceToString(req.NodeTags),
Show: req.Show,
Sell: req.Sell,
Sort: req.Sort,
@@ -1,125 +0,0 @@
package system
import (
"context"
"github.com/perfect-panel/server/internal/model/application"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
)
type CreateApplicationLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewCreateApplicationLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateApplicationLogic {
return &CreateApplicationLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *CreateApplicationLogic) CreateApplication(req *types.CreateApplicationRequest) error {
var ios []application.ApplicationVersion
if len(req.Platform.IOS) > 0 {
for _, ios_ := range req.Platform.IOS {
ios = append(ios, application.ApplicationVersion{
Url: ios_.Url,
Version: ios_.Version,
Platform: "ios",
IsDefault: ios_.IsDefault,
Description: ios_.Description,
})
}
}
var mac []application.ApplicationVersion
if len(req.Platform.MacOS) > 0 {
for _, mac_ := range req.Platform.MacOS {
mac = append(mac, application.ApplicationVersion{
Url: mac_.Url,
Version: mac_.Version,
Platform: "macos",
IsDefault: mac_.IsDefault,
Description: mac_.Description,
})
}
}
var linux []application.ApplicationVersion
if len(req.Platform.Linux) > 0 {
for _, linux_ := range req.Platform.Linux {
linux = append(linux, application.ApplicationVersion{
Url: linux_.Url,
Version: linux_.Version,
Platform: "linux",
IsDefault: linux_.IsDefault,
Description: linux_.Description,
})
}
}
var android []application.ApplicationVersion
if len(req.Platform.Android) > 0 {
for _, android_ := range req.Platform.Android {
android = append(android, application.ApplicationVersion{
Url: android_.Url,
Version: android_.Version,
Platform: "android",
IsDefault: android_.IsDefault,
Description: android_.Description,
})
}
}
var windows []application.ApplicationVersion
if len(req.Platform.Windows) > 0 {
for _, windows_ := range req.Platform.Windows {
windows = append(windows, application.ApplicationVersion{
Url: windows_.Url,
Version: windows_.Version,
Platform: "windows",
IsDefault: windows_.IsDefault,
Description: windows_.Description,
})
}
}
var harmony []application.ApplicationVersion
if len(req.Platform.Harmony) > 0 {
for _, harmony_ := range req.Platform.Harmony {
harmony = append(harmony, application.ApplicationVersion{
Url: harmony_.Url,
Version: harmony_.Version,
Platform: "harmony",
IsDefault: harmony_.IsDefault,
Description: harmony_.Description,
})
}
}
var applicationVersions []application.ApplicationVersion
applicationVersions = append(applicationVersions, ios...)
applicationVersions = append(applicationVersions, mac...)
applicationVersions = append(applicationVersions, linux...)
applicationVersions = append(applicationVersions, android...)
applicationVersions = append(applicationVersions, windows...)
applicationVersions = append(applicationVersions, harmony...)
app := application.Application{
Name: req.Name,
Icon: req.Icon,
SubscribeType: req.SubscribeType,
ApplicationVersions: applicationVersions,
}
err := l.svcCtx.ApplicationModel.Insert(l.ctx, &app)
if err != nil {
l.Errorw("[CreateApplicationLogic] create application error: ", logger.Field("error", err.Error()))
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "create application error: %v", err)
}
return nil
}
@@ -1,44 +0,0 @@
package system
import (
"context"
"github.com/perfect-panel/server/internal/model/application"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
)
type CreateApplicationVersionLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// Create application version
func NewCreateApplicationVersionLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateApplicationVersionLogic {
return &CreateApplicationVersionLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *CreateApplicationVersionLogic) CreateApplicationVersion(req *types.CreateApplicationVersionRequest) error {
create := &application.ApplicationVersion{
Url: req.Url,
Platform: req.Platform,
Version: req.Version,
Description: req.Description,
IsDefault: req.IsDefault,
ApplicationId: req.ApplicationId,
}
err := l.svcCtx.ApplicationModel.InsertVersion(l.ctx, create)
if err != nil {
l.Errorw("[CreateApplicationVersion] create application version error: ", logger.Field("error", err.Error()))
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "create application version error: %v", err)
}
return nil
}
@@ -1,35 +0,0 @@
package system
import (
"context"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
)
type DeleteApplicationLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewDeleteApplicationLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeleteApplicationLogic {
return &DeleteApplicationLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *DeleteApplicationLogic) DeleteApplication(req *types.DeleteApplicationRequest) error {
// delete application
err := l.svcCtx.ApplicationModel.Delete(l.ctx, req.Id)
if err != nil {
l.Errorw("[DeleteApplicationLogic] delete application error: ", logger.Field("error", err.Error()))
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseDeletedError), "delete application error: %v", err.Error())
}
return nil
}
@@ -1,36 +0,0 @@
package system
import (
"context"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
)
type DeleteApplicationVersionLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// Delete application
func NewDeleteApplicationVersionLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeleteApplicationVersionLogic {
return &DeleteApplicationVersionLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *DeleteApplicationVersionLogic) DeleteApplicationVersion(req *types.DeleteApplicationVersionRequest) error {
// delete application
err := l.svcCtx.ApplicationModel.DeleteVersion(l.ctx, req.Id)
if err != nil {
l.Errorw("[DeleteApplicationVersion] delete application version error: ", logger.Field("error", err.Error()))
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseDeletedError), "delete application version error: %v", err.Error())
}
return nil
}
@@ -1,49 +0,0 @@
package system
import (
"context"
"strings"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
"gorm.io/gorm"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
)
type GetApplicationConfigLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// get application config
func NewGetApplicationConfigLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetApplicationConfigLogic {
return &GetApplicationConfigLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetApplicationConfigLogic) GetApplicationConfig() (resp *types.ApplicationConfig, err error) {
resp = &types.ApplicationConfig{}
appConfig, err := l.svcCtx.ApplicationModel.FindOneConfig(l.ctx, 1)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
err = nil
return
}
l.Errorw("[GetApplicationConfig] Database Error", logger.Field("error", err.Error()))
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "get app config error: %v", err.Error())
}
resp.AppId = appConfig.AppId
resp.EncryptionKey = appConfig.EncryptionKey
resp.EncryptionMethod = appConfig.EncryptionMethod
resp.Domains = strings.Split(appConfig.Domains, ";")
resp.StartupPicture = appConfig.StartupPicture
resp.StartupPictureSkipTime = appConfig.StartupPictureSkipTime
return
}
@@ -1,113 +0,0 @@
package system
import (
"context"
"github.com/perfect-panel/server/internal/model/application"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
"gorm.io/gorm"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
)
type GetApplicationLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// Get application
func NewGetApplicationLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetApplicationLogic {
return &GetApplicationLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetApplicationLogic) GetApplication() (resp *types.ApplicationResponse, err error) {
resp = &types.ApplicationResponse{}
var applications []*application.Application
err = l.svcCtx.ApplicationModel.Transaction(l.ctx, func(tx *gorm.DB) (err error) {
return tx.Model(applications).Preload("ApplicationVersions").Find(&applications).Error
})
if err != nil {
l.Errorw("[GetApplicationLogic] get application error: ", logger.Field("error", err.Error()))
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "get application error: %v", err.Error())
}
if len(applications) == 0 {
return resp, nil
}
for _, app := range applications {
applicationResponse := types.ApplicationResponseInfo{
Id: app.Id,
Name: app.Name,
Icon: app.Icon,
Description: app.Description,
SubscribeType: app.SubscribeType,
}
applicationVersions := app.ApplicationVersions
if len(applicationVersions) != 0 {
for _, applicationVersion := range applicationVersions {
switch applicationVersion.Platform {
case "ios":
applicationResponse.Platform.IOS = append(applicationResponse.Platform.IOS, &types.ApplicationVersion{
Id: applicationVersion.Id,
Url: applicationVersion.Url,
Version: applicationVersion.Version,
IsDefault: applicationVersion.IsDefault,
Description: applicationVersion.Description,
})
case "macos":
applicationResponse.Platform.MacOS = append(applicationResponse.Platform.MacOS, &types.ApplicationVersion{
Id: applicationVersion.Id,
Url: applicationVersion.Url,
Version: applicationVersion.Version,
IsDefault: applicationVersion.IsDefault,
Description: applicationVersion.Description,
})
case "linux":
applicationResponse.Platform.Linux = append(applicationResponse.Platform.Linux, &types.ApplicationVersion{
Id: applicationVersion.Id,
Url: applicationVersion.Url,
Version: applicationVersion.Version,
IsDefault: applicationVersion.IsDefault,
Description: applicationVersion.Description,
})
case "android":
applicationResponse.Platform.Android = append(applicationResponse.Platform.Android, &types.ApplicationVersion{
Id: applicationVersion.Id,
Url: applicationVersion.Url,
Version: applicationVersion.Version,
IsDefault: applicationVersion.IsDefault,
Description: applicationVersion.Description,
})
case "windows":
applicationResponse.Platform.Windows = append(applicationResponse.Platform.Windows, &types.ApplicationVersion{
Id: applicationVersion.Id,
Url: applicationVersion.Url,
Version: applicationVersion.Version,
IsDefault: applicationVersion.IsDefault,
Description: applicationVersion.Description,
})
case "harmony":
applicationResponse.Platform.Harmony = append(applicationResponse.Platform.Harmony, &types.ApplicationVersion{
Id: applicationVersion.Id,
Url: applicationVersion.Url,
Version: applicationVersion.Version,
IsDefault: applicationVersion.IsDefault,
Description: applicationVersion.Description,
})
}
}
}
resp.Applications = append(resp.Applications, applicationResponse)
}
return
}
@@ -2,7 +2,8 @@ package system
import (
"context"
"encoding/json"
"github.com/perfect-panel/server/internal/config"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
@@ -26,15 +27,45 @@ func NewGetNodeConfigLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Get
}
func (l *GetNodeConfigLogic) GetNodeConfig() (*types.NodeConfig, error) {
resp := &types.NodeConfig{}
// get server config from db
configs, err := l.svcCtx.SystemModel.GetNodeConfig(l.ctx)
if err != nil {
l.Errorw("[GetNodeConfigLogic] GetNodeConfig get server config error: ", logger.Field("error", err.Error()))
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "GetNodeConfig get server config error: %v", err.Error())
}
// reflect to response
tool.SystemConfigSliceReflectToStruct(configs, resp)
return resp, nil
var dbConfig config.NodeDBConfig
tool.SystemConfigSliceReflectToStruct(configs, &dbConfig)
c := &types.NodeConfig{
NodeSecret: dbConfig.NodeSecret,
NodePullInterval: dbConfig.NodePullInterval,
NodePushInterval: dbConfig.NodePushInterval,
IPStrategy: dbConfig.IPStrategy,
TrafficReportThreshold: dbConfig.TrafficReportThreshold,
}
if dbConfig.DNS != "" {
var dns []types.NodeDNS
err = json.Unmarshal([]byte(dbConfig.DNS), &dns)
if err != nil {
logger.Errorf("[Node] Unmarshal DNS config error: %s", err.Error())
panic(err)
}
c.DNS = dns
}
if dbConfig.Block != "" {
var block []string
_ = json.Unmarshal([]byte(dbConfig.Block), &block)
c.Block = tool.RemoveDuplicateElements(block...)
}
if dbConfig.Outbound != "" {
var outbound []types.NodeOutbound
err = json.Unmarshal([]byte(dbConfig.Outbound), &outbound)
if err != nil {
logger.Errorf("[Node] Unmarshal Outbound config error: %s", err.Error())
panic(err)
}
c.Outbound = outbound
}
return c, nil
}
@@ -1,42 +0,0 @@
package system
import (
"context"
"github.com/perfect-panel/server/internal/model/subscribeType"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
)
type GetSubscribeTypeLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logger.Logger
}
func NewGetSubscribeTypeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetSubscribeTypeLogic {
return &GetSubscribeTypeLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logger.WithContext(ctx),
}
}
func (l *GetSubscribeTypeLogic) GetSubscribeType() (resp *types.SubscribeType, err error) {
var list []*subscribeType.SubscribeType
err = l.svcCtx.DB.Model(&subscribeType.SubscribeType{}).Find(&list).Error
if err != nil {
l.Errorw("[GetSubscribeType] get subscribe type failed", logger.Field("error", err.Error()))
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "get subscribe type failed: %v", err)
}
typeList := make([]string, 0)
for _, item := range list {
typeList = append(typeList, item.Name)
}
return &types.SubscribeType{
SubscribeTypes: typeList,
}, nil
}
@@ -0,0 +1,33 @@
package system
import (
"context"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"time"
)
type PreViewNodeMultiplierLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// PreView Node Multiplier
func NewPreViewNodeMultiplierLogic(ctx context.Context, svcCtx *svc.ServiceContext) *PreViewNodeMultiplierLogic {
return &PreViewNodeMultiplierLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *PreViewNodeMultiplierLogic) PreViewNodeMultiplier() (resp *types.PreViewNodeMultiplierResponse, err error) {
now := time.Now()
ratio := l.svcCtx.NodeMultiplierManager.GetMultiplier(now)
return &types.PreViewNodeMultiplierResponse{
Ratio: ratio,
CurrentTime: now.Format("2006-01-02 15:04:05"),
}, nil
}
@@ -1,45 +0,0 @@
package system
import (
"context"
"strings"
"github.com/perfect-panel/server/internal/model/application"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
)
type UpdateApplicationConfigLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// update application config
func NewUpdateApplicationConfigLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateApplicationConfigLogic {
return &UpdateApplicationConfigLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *UpdateApplicationConfigLogic) UpdateApplicationConfig(req *types.ApplicationConfig) error {
err := l.svcCtx.ApplicationModel.UpdateConfig(l.ctx, &application.ApplicationConfig{
Id: 1,
AppId: req.AppId,
EncryptionKey: req.EncryptionKey,
EncryptionMethod: req.EncryptionMethod,
Domains: strings.Join(req.Domains, ";"),
StartupPicture: req.StartupPicture,
StartupPictureSkipTime: req.StartupPictureSkipTime,
})
if err != nil {
l.Errorw("[UpdateApplicationConfig] Database Error", logger.Field("error", err.Error()))
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update app config error: %v", err.Error())
}
return nil
}
@@ -1,149 +0,0 @@
package system
import (
"context"
"github.com/perfect-panel/server/internal/model/application"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
"gorm.io/gorm"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
)
type UpdateApplicationLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewUpdateApplicationLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateApplicationLogic {
return &UpdateApplicationLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *UpdateApplicationLogic) UpdateApplication(req *types.UpdateApplicationRequest) error {
// find application
app, err := l.svcCtx.ApplicationModel.FindOne(l.ctx, req.Id)
if err != nil {
l.Errorw("[UpdateApplication] find application error", logger.Field("error", err.Error()))
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find application error: %v", err.Error())
}
app.Name = req.Name
app.Icon = req.Icon
app.SubscribeType = req.SubscribeType
app.Description = req.Description
var ios []application.ApplicationVersion
if len(req.Platform.IOS) > 0 {
for _, ios_ := range req.Platform.IOS {
ios = append(ios, application.ApplicationVersion{
Url: ios_.Url,
Version: ios_.Version,
Platform: "ios",
IsDefault: ios_.IsDefault,
Description: ios_.Description,
ApplicationId: app.Id,
})
}
}
var mac []application.ApplicationVersion
if len(req.Platform.MacOS) > 0 {
for _, mac_ := range req.Platform.MacOS {
mac = append(mac, application.ApplicationVersion{
Url: mac_.Url,
Version: mac_.Version,
Platform: "macos",
IsDefault: mac_.IsDefault,
Description: mac_.Description,
ApplicationId: app.Id,
})
}
}
var linux []application.ApplicationVersion
if len(req.Platform.Linux) > 0 {
for _, linux_ := range req.Platform.Linux {
linux = append(linux, application.ApplicationVersion{
Url: linux_.Url,
Version: linux_.Version,
Platform: "linux",
IsDefault: linux_.IsDefault,
Description: linux_.Description,
ApplicationId: app.Id,
})
}
}
var android []application.ApplicationVersion
if len(req.Platform.Android) > 0 {
for _, android_ := range req.Platform.Android {
android = append(android, application.ApplicationVersion{
Url: android_.Url,
Version: android_.Version,
Platform: "android",
IsDefault: android_.IsDefault,
Description: android_.Description,
ApplicationId: app.Id,
})
}
}
var windows []application.ApplicationVersion
if len(req.Platform.Windows) > 0 {
for _, windows_ := range req.Platform.Windows {
windows = append(windows, application.ApplicationVersion{
Url: windows_.Url,
Version: windows_.Version,
Platform: "windows",
IsDefault: windows_.IsDefault,
Description: windows_.Description,
ApplicationId: app.Id,
})
}
}
var harmony []application.ApplicationVersion
if len(req.Platform.Harmony) > 0 {
for _, harmony_ := range req.Platform.Harmony {
harmony = append(harmony, application.ApplicationVersion{
Url: harmony_.Url,
Version: harmony_.Version,
Platform: "harmony",
IsDefault: harmony_.IsDefault,
Description: harmony_.Description,
ApplicationId: app.Id,
})
}
}
var applicationVersions []application.ApplicationVersion
applicationVersions = append(applicationVersions, ios...)
applicationVersions = append(applicationVersions, mac...)
applicationVersions = append(applicationVersions, linux...)
applicationVersions = append(applicationVersions, android...)
applicationVersions = append(applicationVersions, windows...)
applicationVersions = append(applicationVersions, harmony...)
app.ApplicationVersions = applicationVersions
err = l.svcCtx.ApplicationModel.Transaction(l.ctx, func(db *gorm.DB) error {
if err = db.Where("application_id = ?", app.Id).Delete(&application.ApplicationVersion{}).Error; err != nil {
return err
}
if err = db.Create(&applicationVersions).Error; err != nil {
return err
}
return db.Save(app).Error
})
if err != nil {
l.Errorw("[UpdateApplication] update application error", logger.Field("error", err.Error()))
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update application error: %v", err.Error())
}
return nil
}
@@ -1,45 +0,0 @@
package system
import (
"context"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
)
type UpdateApplicationVersionLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// Update application version
func NewUpdateApplicationVersionLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateApplicationVersionLogic {
return &UpdateApplicationVersionLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *UpdateApplicationVersionLogic) UpdateApplicationVersion(req *types.UpdateApplicationVersionRequest) error {
// find application
app, err := l.svcCtx.ApplicationModel.FindOneVersion(l.ctx, req.Id)
if err != nil {
l.Errorw("[UpdateApplicationVersion] find application version error", logger.Field("error", err.Error()))
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find application error: %v", err.Error())
}
app.Url = req.Url
app.Version = req.Version
app.Description = req.Description
app.IsDefault = req.IsDefault
err = l.svcCtx.ApplicationModel.UpdateVersion(l.ctx, app)
if err != nil {
l.Errorw("[UpdateApplicationVersion] update application version error", logger.Field("error", err.Error()))
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update application version error: %v", err.Error())
}
return nil
}
@@ -41,7 +41,9 @@ func (l *UpdateNodeConfigLogic) UpdateNodeConfig(req *types.NodeConfig) error {
// Get the field name
fieldName := t.Field(i).Name
// Get the field value to string
fieldValue := tool.ConvertValueToString(v.Field(i))
var fieldValue string
fieldValue = tool.ConvertValueToString(v.Field(i))
// Update the server config
err = db.Model(&system.System{}).Where("`category` = 'server' and `key` = ?", fieldName).Update("value", fieldValue).Error
if err != nil {
@@ -0,0 +1,51 @@
package tool
import (
"context"
"fmt"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/constant"
"github.com/perfect-panel/server/pkg/logger"
)
type GetVersionLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// NewGetVersionLogic Get Version
func NewGetVersionLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetVersionLogic {
return &GetVersionLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetVersionLogic) GetVersion() (resp *types.VersionResponse, err error) {
version := constant.Version
buildTime := constant.BuildTime
// Normalize unknown values
if version == "unknown version" {
version = "unknown"
}
if buildTime == "unknown time" {
buildTime = "unknown"
}
// Format version based on whether it starts with 'v'
var formattedVersion string
if len(version) > 0 && version[0] == 'v' {
formattedVersion = fmt.Sprintf("%s(%s)", version[1:], buildTime)
} else {
formattedVersion = fmt.Sprintf("%s(%s) Develop", version, buildTime)
}
return &types.VersionResponse{
Version: formattedVersion,
}, nil
}
+6 -4
View File
@@ -39,10 +39,12 @@ func (l *CreateUserLogic) CreateUser(req *types.CreateUserRequest) error {
}
pwd := tool.EncodePassWord(req.Password)
newUser := &user.User{
Password: pwd,
ReferCode: req.ReferCode,
Balance: req.Balance,
IsAdmin: &req.IsAdmin,
Password: pwd,
ReferralPercentage: req.ReferralPercentage,
OnlyFirstPurchase: &req.OnlyFirstPurchase,
ReferCode: req.ReferCode,
Balance: req.Balance,
IsAdmin: &req.IsAdmin,
}
var ams []user.AuthMethods
@@ -77,5 +77,9 @@ func (l *CreateUserSubscribeLogic) CreateUserSubscribe(req *types.CreateUserSubs
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "UpdateUserCache error: %v", err.Error())
}
err = l.svcCtx.SubscribeModel.ClearCache(l.ctx, userSub.SubscribeId)
if err != nil {
logger.Errorw("ClearSubscribe error", logger.Field("error", err.Error()))
}
return nil
}
@@ -26,10 +26,27 @@ func NewDeleteUserSubscribeLogic(ctx context.Context, svcCtx *svc.ServiceContext
}
func (l *DeleteUserSubscribeLogic) DeleteUserSubscribe(req *types.DeleteUserSubscribeRequest) error {
err := l.svcCtx.UserModel.DeleteSubscribeById(l.ctx, req.UserSubscribeId)
// find user subscribe by ID
userSubscribe, err := l.svcCtx.UserModel.FindOneSubscribe(l.ctx, req.UserSubscribeId)
if err != nil {
l.Errorw("failed to find user subscribe", logger.Field("error", err.Error()), logger.Field("userSubscribeId", req.UserSubscribeId))
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "failed to find user subscribe: %v", err.Error())
}
err = l.svcCtx.UserModel.DeleteSubscribeById(l.ctx, req.UserSubscribeId)
if err != nil {
l.Errorw("failed to delete user subscribe", logger.Field("error", err.Error()), logger.Field("userSubscribeId", req.UserSubscribeId))
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseDeletedError), "failed to delete user subscribe: %v", err.Error())
}
// Clear user subscribe cache
if err = l.svcCtx.UserModel.ClearSubscribeCache(l.ctx, userSubscribe); err != nil {
l.Errorw("failed to clear user subscribe cache", logger.Field("error", err.Error()), logger.Field("userSubscribeId", req.UserSubscribeId))
return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "failed to clear user subscribe cache: %v", err.Error())
}
// Clear subscribe cache
if err = l.svcCtx.SubscribeModel.ClearCache(l.ctx, userSubscribe.SubscribeId); err != nil {
l.Errorw("failed to clear subscribe cache", logger.Field("error", err.Error()), logger.Field("subscribeId", userSubscribe.SubscribeId))
return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "failed to clear subscribe cache: %v", err.Error())
}
return nil
}
@@ -32,6 +32,7 @@ func (l *GetUserListLogic) GetUserList(req *types.GetUserListRequest) (*types.Ge
Search: req.Search,
SubscribeId: req.SubscribeId,
UserSubscribeId: req.UserSubscribeId,
Order: "DESC",
})
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "GetUserListLogic failed: %v", err.Error())
@@ -40,20 +41,20 @@ func (l *GetUserListLogic) GetUserList(req *types.GetUserListRequest) (*types.Ge
userRespList := make([]types.User, 0, len(list))
for _, item := range list {
var user types.User
tool.DeepCopy(&user, item)
var u types.User
tool.DeepCopy(&u, item)
// 处理 AuthMethods
authMethods := make([]types.UserAuthMethod, len(user.AuthMethods)) // 直接创建目标 slice
for i, method := range user.AuthMethods {
authMethods := make([]types.UserAuthMethod, len(u.AuthMethods)) // 直接创建目标 slice
for i, method := range u.AuthMethods {
tool.DeepCopy(&authMethods[i], method)
if method.AuthType == "mobile" {
authMethods[i].AuthIdentifier = phone.FormatToInternational(method.AuthIdentifier)
}
}
user.AuthMethods = authMethods
u.AuthMethods = authMethods
userRespList = append(userRespList, user)
userRespList = append(userRespList, u)
}
return &types.GetUserListResponse{
@@ -3,11 +3,10 @@ package user
import (
"context"
"github.com/perfect-panel/server/internal/model/user"
"github.com/perfect-panel/server/internal/model/log"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/tool"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
)
@@ -28,15 +27,34 @@ func NewGetUserLoginLogsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *
}
func (l *GetUserLoginLogsLogic) GetUserLoginLogs(req *types.GetUserLoginLogsRequest) (resp *types.GetUserLoginLogsResponse, err error) {
data, total, err := l.svcCtx.UserModel.FilterLoginLogList(l.ctx, req.Page, req.Size, &user.LoginLogFilterParams{
UserId: req.UserId,
data, total, err := l.svcCtx.LogModel.FilterSystemLog(l.ctx, &log.FilterParams{
Page: req.Page,
Size: req.Size,
Type: log.TypeLogin.Uint8(),
ObjectID: req.UserId,
})
if err != nil {
l.Errorw("[GetUserLoginLogs] get user login logs failed", logger.Field("error", err.Error()), logger.Field("request", req))
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "get user login logs failed: %v", err.Error())
}
var list []types.UserLoginLog
tool.DeepCopy(&list, data)
for _, datum := range data {
var content log.Login
if err = content.Unmarshal([]byte(datum.Content)); err != nil {
l.Errorf("[GetUserLoginLogs] unmarshal login log content failed: %v", err.Error())
continue
}
list = append(list, types.UserLoginLog{
Id: datum.Id,
UserId: datum.ObjectID,
LoginIP: content.LoginIP,
UserAgent: content.UserAgent,
Success: content.Success,
Timestamp: datum.CreatedAt.UnixMilli(),
})
}
return &types.GetUserLoginLogsResponse{
Total: total,
List: list,
@@ -3,7 +3,7 @@ package user
import (
"context"
"github.com/perfect-panel/server/internal/model/user"
"github.com/perfect-panel/server/internal/model/log"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
@@ -28,10 +28,7 @@ func NewGetUserSubscribeLogsLogic(ctx context.Context, svcCtx *svc.ServiceContex
}
func (l *GetUserSubscribeLogsLogic) GetUserSubscribeLogs(req *types.GetUserSubscribeLogsRequest) (resp *types.GetUserSubscribeLogsResponse, err error) {
data, total, err := l.svcCtx.UserModel.FilterSubscribeLogList(l.ctx, req.Page, req.Size, &user.SubscribeLogFilterParams{
UserSubscribeId: req.SubscribeId,
UserId: req.UserId,
})
data, total, err := l.svcCtx.LogModel.FilterSystemLog(l.ctx, &log.FilterParams{})
if err != nil {
l.Errorw("[GetUserSubscribeLogs] Get User Subscribe Logs Error:", logger.Field("err", err.Error()))
@@ -0,0 +1,62 @@
package user
import (
"context"
"github.com/perfect-panel/server/internal/model/log"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
)
type GetUserSubscribeResetTrafficLogsLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// Get user subcribe reset traffic logs
func NewGetUserSubscribeResetTrafficLogsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetUserSubscribeResetTrafficLogsLogic {
return &GetUserSubscribeResetTrafficLogsLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetUserSubscribeResetTrafficLogsLogic) GetUserSubscribeResetTrafficLogs(req *types.GetUserSubscribeResetTrafficLogsRequest) (resp *types.GetUserSubscribeResetTrafficLogsResponse, err error) {
data, total, err := l.svcCtx.LogModel.FilterSystemLog(l.ctx, &log.FilterParams{
Page: req.Page,
Size: req.Size,
Type: log.TypeResetSubscribe.Uint8(),
ObjectID: req.UserSubscribeId,
})
if err != nil {
l.Errorf("[ResetSubscribeTrafficLog] failed to filter system log: %v", err)
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "FilterSystemLog failed, err: %v", err)
}
var list []types.ResetSubscribeTrafficLog
for _, item := range data {
var content log.ResetSubscribe
if err = content.Unmarshal([]byte(item.Content)); err != nil {
l.Errorf("[ResetSubscribeTrafficLog] failed to unmarshal log: %v", err)
continue
}
list = append(list, types.ResetSubscribeTrafficLog{
Id: item.Id,
Type: content.Type,
OrderNo: content.OrderNo,
Timestamp: content.Timestamp,
UserSubscribeId: item.ObjectID,
})
}
return &types.GetUserSubscribeResetTrafficLogsResponse{
Total: total,
List: list,
}, nil
}
@@ -31,11 +31,21 @@ func (l *UpdateUserAuthMethodLogic) UpdateUserAuthMethod(req *types.UpdateUserAu
l.Errorw("Get user auth method error", logger.Field("error", err.Error()), logger.Field("userId", req.UserId), logger.Field("authType", req.AuthType))
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Get user auth method error: %v", err.Error())
}
userInfo, err := l.svcCtx.UserModel.FindOne(l.ctx, req.UserId)
if err != nil {
l.Errorw("Get user info error", logger.Field("error", err.Error()), logger.Field("userId", req.UserId))
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Get user info error: %v", err.Error())
}
method.AuthType = req.AuthType
method.AuthIdentifier = req.AuthIdentifier
if err = l.svcCtx.UserModel.UpdateUserAuthMethods(l.ctx, method); err != nil {
l.Errorw("Update user auth method error", logger.Field("error", err.Error()), logger.Field("userId", req.UserId), logger.Field("authType", req.AuthType))
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "Update user auth method error: %v", err.Error())
}
if err = l.svcCtx.UserModel.UpdateUserCache(l.ctx, userInfo); err != nil {
l.Errorw("Update user cache error", logger.Field("error", err.Error()), logger.Field("userId", req.UserId))
return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "Update user cache error: %v", err.Error())
}
return nil
}
@@ -4,7 +4,9 @@ import (
"context"
"os"
"strings"
"time"
"github.com/perfect-panel/server/internal/model/log"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/logger"
@@ -37,17 +39,90 @@ func (l *UpdateUserBasicInfoLogic) UpdateUserBasicInfo(req *types.UpdateUserBasi
isDemo := strings.ToLower(os.Getenv("PPANEL_MODE")) == "demo"
tool.DeepCopy(userInfo, req)
if req.Avatar != "" && !tool.IsValidImageSize(req.Avatar, 1024) {
return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "Invalid Image Size")
}
userInfo.Balance = req.Balance
userInfo.GiftAmount = req.GiftAmount
userInfo.Commission = req.Commission
userInfo.Remark = req.Remark
// 手动设置 IsAdmin 字段,因为类型不匹配(*bool vs bool
userInfo.IsAdmin = &req.IsAdmin
userInfo.Enable = &req.Enable
if userInfo.Balance != req.Balance {
change := req.Balance - userInfo.Balance
balanceLog := log.Balance{
Type: log.BalanceTypeAdjust,
Amount: change,
OrderNo: "",
Balance: req.Balance,
Timestamp: time.Now().UnixMilli(),
}
content, _ := balanceLog.Marshal()
err = l.svcCtx.LogModel.Insert(l.ctx, &log.SystemLog{
Type: log.TypeBalance.Uint8(),
Date: time.Now().Format(time.DateOnly),
ObjectID: userInfo.Id,
Content: string(content),
})
if err != nil {
l.Errorw("[UpdateUserBasicInfoLogic] Insert Balance Log Error:", logger.Field("err", err.Error()), logger.Field("userId", req.UserId))
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "Insert Balance Log Error")
}
userInfo.Balance = req.Balance
}
if userInfo.GiftAmount != req.GiftAmount {
change := req.GiftAmount - userInfo.GiftAmount
if change != 0 {
var changeType uint16
if userInfo.GiftAmount < req.GiftAmount {
changeType = log.GiftTypeIncrease
} else {
changeType = log.GiftTypeReduce
}
giftLog := log.Gift{
Type: changeType,
Amount: change,
Balance: req.GiftAmount,
Remark: "Admin adjustment",
Timestamp: time.Now().UnixMilli(),
}
content, _ := giftLog.Marshal()
// Add gift amount change log
err = l.svcCtx.LogModel.Insert(l.ctx, &log.SystemLog{
Type: log.TypeGift.Uint8(),
Date: time.Now().Format(time.DateOnly),
ObjectID: userInfo.Id,
Content: string(content),
})
if err != nil {
l.Errorw("[UpdateUserBasicInfoLogic] Insert Balance Log Error:", logger.Field("err", err.Error()), logger.Field("userId", req.UserId))
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "Insert Balance Log Error")
}
userInfo.GiftAmount = req.GiftAmount
}
}
if req.Commission != userInfo.Commission {
commentLog := log.Commission{
Type: log.CommissionTypeAdjust,
Amount: req.Commission - userInfo.Commission,
Timestamp: time.Now().UnixMilli(),
}
content, _ := commentLog.Marshal()
err = l.svcCtx.LogModel.Insert(l.ctx, &log.SystemLog{
Type: log.TypeCommission.Uint8(),
Date: time.Now().Format(time.DateOnly),
ObjectID: userInfo.Id,
Content: string(content),
})
if err != nil {
l.Errorw("[UpdateUserBasicInfoLogic] Insert Commission Log Error:", logger.Field("err", err.Error()), logger.Field("userId", req.UserId))
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "Insert Commission Log Error")
}
userInfo.Commission = req.Commission
}
tool.DeepCopy(userInfo, req)
userInfo.OnlyFirstPurchase = &req.OnlyFirstPurchase
userInfo.ReferralPercentage = req.ReferralPercentage
if req.Password != "" {
if userInfo.Id == 2 && isDemo {
@@ -28,7 +28,7 @@ func NewUpdateUserSubscribeLogic(ctx context.Context, svcCtx *svc.ServiceContext
}
func (l *UpdateUserSubscribeLogic) UpdateUserSubscribe(req *types.UpdateUserSubscribeRequest) error {
userSub, err := l.svcCtx.UserModel.FindOneUserSubscribe(l.ctx, req.UserSubscribeId)
userSub, err := l.svcCtx.UserModel.FindOneSubscribe(l.ctx, req.UserSubscribeId)
if err != nil {
l.Errorw("FindOneUserSubscribe failed:", logger.Field("error", err.Error()), logger.Field("userSubscribeId", req.UserSubscribeId))
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "FindOneUserSubscribe failed: %v", err.Error())
@@ -59,6 +59,15 @@ func (l *UpdateUserSubscribeLogic) UpdateUserSubscribe(req *types.UpdateUserSubs
l.Errorw("UpdateSubscribe failed:", logger.Field("error", err.Error()))
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "UpdateSubscribe failed: %v", err.Error())
}
// Clear user subscribe cache
if err = l.svcCtx.UserModel.ClearSubscribeCache(l.ctx, userSub); err != nil {
l.Errorw("ClearSubscribeCache failed:", logger.Field("error", err.Error()), logger.Field("userSubscribeId", userSub.Id))
return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "ClearSubscribeCache failed: %v", err.Error())
}
// Clear subscribe cache
if err = l.svcCtx.SubscribeModel.ClearCache(l.ctx, userSub.SubscribeId); err != nil {
l.Errorw("failed to clear subscribe cache", logger.Field("error", err.Error()), logger.Field("subscribeId", userSub.SubscribeId))
return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "failed to clear subscribe cache: %v", err.Error())
}
return nil
}