Compare commits
10 Commits
3fb23e3106
...
6dcd75a39b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6dcd75a39b | ||
|
|
29dd54546d | ||
|
|
41d8de42c0 | ||
|
|
b97e2d650e | ||
|
|
c5be0081ca | ||
|
|
1a6cced814 | ||
|
|
bf24e1756a | ||
|
|
4e7a6e9e41 | ||
|
|
6c7d45a640 | ||
|
|
c835e751ac |
@ -114,8 +114,8 @@ proxy services. Built with Go, it emphasizes performance, security, and scalabil
|
|||||||
|
|
||||||
4. **Pull from Docker Hub** (after CI/CD publishes):
|
4. **Pull from Docker Hub** (after CI/CD publishes):
|
||||||
```bash
|
```bash
|
||||||
docker pull yourusername/ppanel-server:latest
|
docker pull ppanel/ppanel-server:latest
|
||||||
docker run --rm -p 8080:8080 yourusername/ppanel-server:latest
|
docker run --rm -p 8080:8080 ppanel/ppanel-server:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
## 📖 API Documentation
|
## 📖 API Documentation
|
||||||
|
|||||||
@ -29,10 +29,12 @@ func NewPreviewSubscribeTemplateLogic(ctx context.Context, svcCtx *svc.ServiceCo
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *PreviewSubscribeTemplateLogic) PreviewSubscribeTemplate(req *types.PreviewSubscribeTemplateRequest) (resp *types.PreviewSubscribeTemplateResponse, err error) {
|
func (l *PreviewSubscribeTemplateLogic) PreviewSubscribeTemplate(req *types.PreviewSubscribeTemplateRequest) (resp *types.PreviewSubscribeTemplateResponse, err error) {
|
||||||
|
enable := true
|
||||||
_, servers, err := l.svcCtx.NodeModel.FilterNodeList(l.ctx, &node.FilterNodeParams{
|
_, servers, err := l.svcCtx.NodeModel.FilterNodeList(l.ctx, &node.FilterNodeParams{
|
||||||
Page: 1,
|
Page: 1,
|
||||||
Size: 1000,
|
Size: 1000,
|
||||||
Preload: true,
|
Preload: true,
|
||||||
|
Enabled: &enable,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Errorf("[PreviewSubscribeTemplateLogic] FindAllServer error: %v", err.Error())
|
l.Errorf("[PreviewSubscribeTemplateLogic] FindAllServer error: %v", err.Error())
|
||||||
|
|||||||
@ -230,7 +230,7 @@ func (l *MigrateServerNodeLogic) adapterServer(info *server.Server) (*node.Serve
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
protocol := node.Protocol{
|
protocol := node.Protocol{
|
||||||
Type: "hysteria2",
|
Type: "hysteria",
|
||||||
Port: uint16(src.Port),
|
Port: uint16(src.Port),
|
||||||
HopPorts: src.HopPorts,
|
HopPorts: src.HopPorts,
|
||||||
HopInterval: src.HopInterval,
|
HopInterval: src.HopInterval,
|
||||||
|
|||||||
@ -8,7 +8,10 @@ const (
|
|||||||
Trojan = "trojan"
|
Trojan = "trojan"
|
||||||
AnyTLS = "anytls"
|
AnyTLS = "anytls"
|
||||||
Tuic = "tuic"
|
Tuic = "tuic"
|
||||||
Hysteria2 = "hysteria2"
|
Hysteria = "hysteria"
|
||||||
|
// Deprecated: Hysteria2 is deprecated, use Hysteria instead
|
||||||
|
// TODO: remove in future versions
|
||||||
|
Hysteria2 = "hysteria2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SecurityConfig struct {
|
type SecurityConfig struct {
|
||||||
|
|||||||
@ -57,6 +57,11 @@ func (l *GetServerConfigLogic) GetServerConfig(req *types.GetServerConfigRequest
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// compatible hysteria2, remove in future versions
|
||||||
|
if req.Protocol == Hysteria2 {
|
||||||
|
req.Protocol = Hysteria
|
||||||
|
}
|
||||||
|
|
||||||
protocols, err := data.UnmarshalProtocols()
|
protocols, err := data.UnmarshalProtocols()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -209,7 +214,7 @@ func (l *GetServerConfigLogic) compatible(config node.Protocol) map[string]inter
|
|||||||
RealityShortId: config.RealityShortId,
|
RealityShortId: config.RealityShortId,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
case Hysteria2:
|
case Hysteria:
|
||||||
result = Hysteria2Node{
|
result = Hysteria2Node{
|
||||||
Port: config.Port,
|
Port: config.Port,
|
||||||
HopPorts: config.HopPorts,
|
HopPorts: config.HopPorts,
|
||||||
|
|||||||
@ -249,8 +249,9 @@ func (l *SubscribeLogic) createExpiredServers() []*node.Node {
|
|||||||
Port: 18080,
|
Port: 18080,
|
||||||
Address: "127.0.0.1",
|
Address: "127.0.0.1",
|
||||||
Server: &node.Server{
|
Server: &node.Server{
|
||||||
|
Id: 1,
|
||||||
Name: "Subscribe Expired",
|
Name: "Subscribe Expired",
|
||||||
Protocols: "[{\"type:\"\"shadowsocks\",\"cipher\":\"aes-256-gcm\",\"port\":1}]",
|
Protocols: "[{\"type\":\"shadowsocks\",\"cipher\":\"aes-256-gcm\",\"port\":1}]",
|
||||||
},
|
},
|
||||||
Protocol: "shadowsocks",
|
Protocol: "shadowsocks",
|
||||||
Enabled: &enable,
|
Enabled: &enable,
|
||||||
@ -261,8 +262,9 @@ func (l *SubscribeLogic) createExpiredServers() []*node.Node {
|
|||||||
Port: 18080,
|
Port: 18080,
|
||||||
Address: "127.0.0.1",
|
Address: "127.0.0.1",
|
||||||
Server: &node.Server{
|
Server: &node.Server{
|
||||||
|
Id: 1,
|
||||||
Name: "Subscribe Expired",
|
Name: "Subscribe Expired",
|
||||||
Protocols: "[{\"type:\"\"shadowsocks\",\"cipher\":\"aes-256-gcm\",\"port\":1}]",
|
Protocols: "[{\"type\":\"shadowsocks\",\"cipher\":\"aes-256-gcm\",\"port\":1}]",
|
||||||
},
|
},
|
||||||
Protocol: "shadowsocks",
|
Protocol: "shadowsocks",
|
||||||
Enabled: &enable,
|
Enabled: &enable,
|
||||||
|
|||||||
@ -3,8 +3,10 @@ package node
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/perfect-panel/server/pkg/tool"
|
"github.com/perfect-panel/server/pkg/tool"
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type customServerLogicModel interface {
|
type customServerLogicModel interface {
|
||||||
@ -85,10 +87,7 @@ func (m *customServerModel) FilterNodeList(ctx context.Context, params *FilterNo
|
|||||||
query = query.Where("server_id IN ?", params.ServerId)
|
query = query.Where("server_id IN ?", params.ServerId)
|
||||||
}
|
}
|
||||||
if len(params.Tag) > 0 {
|
if len(params.Tag) > 0 {
|
||||||
query = query.Where("1 = 0")
|
query = query.Scopes(InSet("tags", params.Tag))
|
||||||
for _, tag := range params.Tag {
|
|
||||||
query = query.Or("FIND_IN_SET(?,tags)", tag)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if params.Protocol != "" {
|
if params.Protocol != "" {
|
||||||
query = query.Where("protocol = ?", params.Protocol)
|
query = query.Where("protocol = ?", params.Protocol)
|
||||||
@ -165,3 +164,22 @@ func (m *customServerModel) ClearServerCache(ctx context.Context, serverId int64
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InSet 支持多值 OR 查询
|
||||||
|
func InSet(field string, values []string) func(db *gorm.DB) *gorm.DB {
|
||||||
|
return func(db *gorm.DB) *gorm.DB {
|
||||||
|
if len(values) == 0 {
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
conds := make([]string, len(values))
|
||||||
|
args := make([]interface{}, len(values))
|
||||||
|
for i, v := range values {
|
||||||
|
conds[i] = "FIND_IN_SET(?, " + field + ")"
|
||||||
|
args[i] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用括号包裹 OR 条件,保证外层 AND 不受影响
|
||||||
|
return db.Where("("+strings.Join(conds, " OR ")+")", args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -7,11 +7,9 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/perfect-panel/server/internal/model/log"
|
"github.com/perfect-panel/server/internal/model/log"
|
||||||
"github.com/perfect-panel/server/internal/model/node"
|
|
||||||
"github.com/perfect-panel/server/pkg/constant"
|
"github.com/perfect-panel/server/pkg/constant"
|
||||||
"github.com/perfect-panel/server/pkg/logger"
|
"github.com/perfect-panel/server/pkg/logger"
|
||||||
|
|
||||||
@ -466,17 +464,8 @@ func (l *ActivateOrderLogic) calculateCommission(price int64, percentage uint8)
|
|||||||
|
|
||||||
// clearServerCache clears user list cache for all servers associated with the subscription
|
// clearServerCache clears user list cache for all servers associated with the subscription
|
||||||
func (l *ActivateOrderLogic) clearServerCache(ctx context.Context, sub *subscribe.Subscribe) {
|
func (l *ActivateOrderLogic) clearServerCache(ctx context.Context, sub *subscribe.Subscribe) {
|
||||||
nodeIds := tool.StringToInt64Slice(sub.Nodes)
|
if err := l.svc.SubscribeModel.ClearCache(ctx, sub.Id); err != nil {
|
||||||
tags := strings.Split(sub.NodeTags, ",")
|
logger.WithContext(ctx).Error("[Order Queue] Clear subscribe cache failed", logger.Field("error", err.Error()))
|
||||||
|
|
||||||
err := l.svc.NodeModel.ClearNodeCache(ctx, &node.FilterNodeParams{
|
|
||||||
Page: 1,
|
|
||||||
Size: 1000,
|
|
||||||
NodeId: nodeIds,
|
|
||||||
Tag: tags,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
logger.WithContext(ctx).Error("[Order Queue] Clear node cache failed", logger.Field("error", err.Error()))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,11 +3,8 @@ package subscription
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/perfect-panel/server/internal/model/node"
|
|
||||||
"github.com/perfect-panel/server/pkg/tool"
|
|
||||||
queue "github.com/perfect-panel/server/queue/types"
|
queue "github.com/perfect-panel/server/queue/types"
|
||||||
|
|
||||||
"github.com/perfect-panel/server/pkg/logger"
|
"github.com/perfect-panel/server/pkg/logger"
|
||||||
@ -33,7 +30,7 @@ func (l *CheckSubscriptionLogic) ProcessTask(ctx context.Context, _ *asynq.Task)
|
|||||||
// Check subscription traffic
|
// Check subscription traffic
|
||||||
err := l.svc.UserModel.Transaction(ctx, func(db *gorm.DB) error {
|
err := l.svc.UserModel.Transaction(ctx, func(db *gorm.DB) error {
|
||||||
var list []*user.Subscribe
|
var list []*user.Subscribe
|
||||||
err := db.Model(&user.Subscribe{}).Where("upload + download >= traffic AND status = 1 AND traffic > 0 ").Find(&list).Error
|
err := db.Model(&user.Subscribe{}).Where("upload + download >= traffic AND status IN (0, 1) AND traffic > 0 ").Find(&list).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorw("[Check Subscription Traffic] Query subscribe failed", logger.Field("error", err.Error()))
|
logger.Errorw("[Check Subscription Traffic] Query subscribe failed", logger.Field("error", err.Error()))
|
||||||
return err
|
return err
|
||||||
@ -78,7 +75,7 @@ func (l *CheckSubscriptionLogic) ProcessTask(ctx context.Context, _ *asynq.Task)
|
|||||||
// Check subscription expire
|
// Check subscription expire
|
||||||
err = l.svc.UserModel.Transaction(ctx, func(db *gorm.DB) error {
|
err = l.svc.UserModel.Transaction(ctx, func(db *gorm.DB) error {
|
||||||
var list []*user.Subscribe
|
var list []*user.Subscribe
|
||||||
err = db.Model(&user.Subscribe{}).Where("`status` = 1 AND `expire_time` < ? AND `expire_time` != ? and `finished_at` IS NULL", time.Now(), time.UnixMilli(0)).Find(&list).Error
|
err = db.Model(&user.Subscribe{}).Where("`status` IN (0, 1) AND `expire_time` < ? AND `expire_time` != ? and `finished_at` IS NULL", time.Now(), time.UnixMilli(0)).Find(&list).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("[Check Subscription] Find subscribe failed", logger.Field("error", err.Error()))
|
logger.Error("[Check Subscription] Find subscribe failed", logger.Field("error", err.Error()))
|
||||||
return err
|
return err
|
||||||
@ -207,31 +204,8 @@ func (l *CheckSubscriptionLogic) clearServerCache(ctx context.Context, userSubs
|
|||||||
}
|
}
|
||||||
|
|
||||||
for sub, _ := range subs {
|
for sub, _ := range subs {
|
||||||
info, err := l.svc.SubscribeModel.FindOne(ctx, sub)
|
if err := l.svc.SubscribeModel.ClearCache(ctx, sub); err != nil {
|
||||||
if err != nil {
|
logger.Errorw("[CheckSubscription] ClearCache failed", logger.Field("error", err.Error()), logger.Field("subscribe_id", sub))
|
||||||
logger.Errorw("[CheckSubscription] FindOne subscribe failed", logger.Field("error", err.Error()), logger.Field("subscribe_id", sub))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if info != nil && info.Id == sub {
|
|
||||||
var nodes []int64
|
|
||||||
if info.Nodes != "" {
|
|
||||||
nodes = tool.StringToInt64Slice(info.Nodes)
|
|
||||||
}
|
|
||||||
var tag []string
|
|
||||||
if info.NodeTags != "" {
|
|
||||||
tag = strings.Split(info.NodeTags, ",")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = l.svc.NodeModel.ClearNodeCache(ctx, &node.FilterNodeParams{
|
|
||||||
Page: 1,
|
|
||||||
Size: 1000,
|
|
||||||
Tag: tag,
|
|
||||||
ServerId: nodes,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
logger.Errorw("[CheckSubscription] ClearNodeCache failed", logger.Field("error", err.Error()), logger.Field("subscribe_id", sub))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,12 +9,10 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/perfect-panel/server/internal/model/log"
|
"github.com/perfect-panel/server/internal/model/log"
|
||||||
"github.com/perfect-panel/server/internal/model/node"
|
|
||||||
"github.com/perfect-panel/server/internal/model/subscribe"
|
"github.com/perfect-panel/server/internal/model/subscribe"
|
||||||
"github.com/perfect-panel/server/internal/model/user"
|
"github.com/perfect-panel/server/internal/model/user"
|
||||||
"github.com/perfect-panel/server/internal/svc"
|
"github.com/perfect-panel/server/internal/svc"
|
||||||
"github.com/perfect-panel/server/pkg/logger"
|
"github.com/perfect-panel/server/pkg/logger"
|
||||||
"github.com/perfect-panel/server/pkg/tool"
|
|
||||||
"github.com/perfect-panel/server/queue/types"
|
"github.com/perfect-panel/server/queue/types"
|
||||||
|
|
||||||
"github.com/hibiken/asynq"
|
"github.com/hibiken/asynq"
|
||||||
@ -598,31 +596,11 @@ func (l *ResetTrafficLogic) clearCache(ctx context.Context, list []*user.Subscri
|
|||||||
}
|
}
|
||||||
|
|
||||||
for sub, _ := range subs {
|
for sub, _ := range subs {
|
||||||
info, err := l.svc.SubscribeModel.FindOne(ctx, sub)
|
if err := l.svc.SubscribeModel.ClearCache(ctx, sub); err != nil {
|
||||||
if err != nil {
|
logger.Errorw("[ResetTraffic] Failed to clear subscription cache",
|
||||||
logger.Errorw("[CheckSubscription] FindOne subscribe failed", logger.Field("error", err.Error()), logger.Field("subscribe_id", sub))
|
logger.Field("subscribeId", sub),
|
||||||
continue
|
logger.Field("error", err.Error()),
|
||||||
}
|
)
|
||||||
if info != nil && info.Id == sub {
|
|
||||||
var nodes []int64
|
|
||||||
if info.Nodes != "" {
|
|
||||||
nodes = tool.StringToInt64Slice(info.Nodes)
|
|
||||||
}
|
|
||||||
var tag []string
|
|
||||||
if info.NodeTags != "" {
|
|
||||||
tag = strings.Split(info.NodeTags, ",")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = l.svc.NodeModel.ClearNodeCache(ctx, &node.FilterNodeParams{
|
|
||||||
Page: 1,
|
|
||||||
Size: 1000,
|
|
||||||
Tag: tag,
|
|
||||||
ServerId: nodes,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
logger.Errorw("[CheckSubscription] ClearNodeCache failed", logger.Field("error", err.Error()), logger.Field("subscribe_id", sub))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -113,8 +113,8 @@ PPanel 服务端是 PPanel 项目的后端组件,为代理服务提供强大
|
|||||||
|
|
||||||
4. **从 Docker Hub 拉取**(CI/CD 发布后):
|
4. **从 Docker Hub 拉取**(CI/CD 发布后):
|
||||||
```bash
|
```bash
|
||||||
docker pull yourusername/ppanel-server:latest
|
docker pull ppanel/ppanel-server:latest
|
||||||
docker run --rm -p 8080:8080 yourusername/ppanel-server:latest
|
docker run --rm -p 8080:8080 ppanel/ppanel-server:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
## 📖 API 文档
|
## 📖 API 文档
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user