refactor: 更新项目引用路径从perfect-panel/ppanel-server到perfect-panel/server
Build docker and publish / build (20.15.1) (push) Failing after 6m27s

feat: 添加版本和构建时间变量
fix: 修正短信队列类型注释错误
style: 清理未使用的代码和测试文件
docs: 更新安装文档中的下载链接
chore: 迁移数据库脚本添加日志和订阅配置
This commit is contained in:
2025-10-13 01:33:03 -07:00
parent 393b42f35a
commit c582087c0f
974 changed files with 23609 additions and 23398 deletions
+134
View File
@@ -0,0 +1,134 @@
package email
import (
"context"
"sync"
"time"
"github.com/perfect-panel/server/pkg/logger"
"gorm.io/gorm"
)
var (
Manager *WorkerManager // 全局调度器实例
once sync.Once // 确保 Scheduler 只被初始化一次
limit sync.RWMutex // 控制并发限制
)
type WorkerManager struct {
db *gorm.DB // 数据库连接
sender Sender // 邮件发送器接口
mutex sync.RWMutex // 读写互斥锁,确保线程安全
workers map[int64]*Worker // 存储所有 Worker 实例
cancels map[int64]context.CancelFunc // 存储每个 Worker 的取消函数
}
func NewWorkerManager(db *gorm.DB, sender Sender) *WorkerManager {
if Manager != nil {
return Manager
}
once.Do(func() {
Manager = &WorkerManager{
db: db,
workers: make(map[int64]*Worker),
cancels: make(map[int64]context.CancelFunc),
sender: sender,
}
})
// 设置定时检查任务
go func() {
for {
// 每隔5分钟检查一次
select {
case <-time.After(1 * time.Minute):
checkWorker()
continue
}
}
}()
return Manager
}
// AddWorker 添加一个新的 Worker 实例
func (m *WorkerManager) AddWorker(id int64) {
m.mutex.Lock()
defer m.mutex.Unlock()
if _, exists := m.workers[id]; !exists {
ctx, cancel := context.WithCancel(context.Background())
worker := NewWorker(ctx, id, m.db, m.sender)
m.workers[id] = worker
m.cancels[id] = cancel
go worker.Start()
logger.Info("Batch Send Email",
logger.Field("message", "Added new worker"),
logger.Field("task_id", id),
)
} else {
logger.Info("Batch Send Email",
logger.Field("message", "Worker already exists"),
logger.Field("task_id", id),
)
}
}
// GetWorker 获取指定任务的 Worker 实例
func (m *WorkerManager) GetWorker(id int64) *Worker {
m.mutex.RLock()
defer m.mutex.RUnlock()
if worker, exists := m.workers[id]; exists {
return worker
} else {
logger.Error("Batch Send Email",
logger.Field("message", "Worker not found"),
logger.Field("task_id", id),
)
return nil
}
}
// RemoveWorker 移除指定任务的 Worker 实例
func (m *WorkerManager) RemoveWorker(id int64) {
m.mutex.Lock()
defer m.mutex.Unlock()
if _, exists := m.workers[id]; exists {
delete(m.workers, id)
if cancelFunc, ok := m.cancels[id]; ok {
cancelFunc() // 调用取消函数
delete(m.cancels, id)
}
logger.Info("Batch Send Email",
logger.Field("message", "Removed worker"),
logger.Field("task_id", id),
)
} else {
logger.Error("Batch Send Email",
logger.Field("message", "Worker not found for removal"),
logger.Field("task_id", id),
)
}
}
func checkWorker() {
if Manager == nil {
// 如果 Manager 未初始化,直接返回
return
}
Manager.mutex.Lock()
defer Manager.mutex.Unlock()
for id, worker := range Manager.workers {
if worker.IsRunning() == 2 {
// 如果Worker已完成,移除它
delete(Manager.workers, id)
if cancelFunc, ok := Manager.cancels[id]; ok {
cancelFunc() // 调用取消函数
delete(Manager.cancels, id)
}
logger.Info("Batch Send Email",
logger.Field("message", "Removed completed worker"),
logger.Field("task_id", id),
)
}
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
package email
import "github.com/perfect-panel/ppanel-server/internal/types"
import "github.com/perfect-panel/server/internal/types"
type Platform int
+2 -2
View File
@@ -4,8 +4,8 @@ import (
"encoding/json"
"fmt"
"github.com/perfect-panel/ppanel-server/pkg/email/smtp"
"github.com/perfect-panel/ppanel-server/pkg/logger"
"github.com/perfect-panel/server/pkg/email/smtp"
"github.com/perfect-panel/server/pkg/logger"
)
type Sender interface {
-1
View File
@@ -282,7 +282,6 @@ const (
</body>
</html>
`
DefaultTrafficExceedEmailTemplate = `<!doctype html>
<html>
<head>
+192
View File
@@ -0,0 +1,192 @@
package email
import (
"context"
"encoding/json"
"time"
"github.com/perfect-panel/server/internal/model/task"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/tool"
"gorm.io/gorm"
)
type ErrorInfo struct {
Error string `json:"error"`
Email string `json:"email"`
Time int64 `json:"time"`
}
type Worker struct {
id int64 // 任务ID
db *gorm.DB // 数据库连接
ctx context.Context // 上下文
sender Sender // 邮件发送器接口
status uint8 // 任务状态,0 表示未运行,1 表示运行中 2 表示已完成
}
func NewWorker(ctx context.Context, id int64, db *gorm.DB, sender Sender) *Worker {
return &Worker{
id: id,
db: db,
ctx: ctx,
sender: sender,
}
}
// GetID 获取Worker的任务ID
func (w *Worker) GetID() int64 {
return w.id
}
// IsRunning 检查Worker是否正在运行
func (w *Worker) IsRunning() uint8 {
return w.status
}
// Start 启动Worker,开始处理任务
func (w *Worker) Start() {
// 检查并发限制
limit.Lock()
defer limit.Unlock()
tx := w.db.WithContext(w.ctx)
var taskInfo task.Task
if err := tx.Model(&task.Task{}).Where("id = ?", w.id).First(&taskInfo).Error; err != nil {
logger.Error("Batch Send Email",
logger.Field("message", "Failed to find task"),
logger.Field("error", err.Error()),
logger.Field("task_id", w.id),
)
return
}
if taskInfo.Status != 0 {
logger.Error("Batch Send Email",
logger.Field("message", "Task already completed or in progress"),
logger.Field("task_id", w.id),
)
return
}
var scope task.EmailScope
if err := json.Unmarshal([]byte(taskInfo.Scope), &scope); err != nil {
logger.Error("Batch Send Email",
logger.Field("message", "Failed to parse task scope"),
logger.Field("error", err.Error()),
logger.Field("task_id", w.id),
)
return
}
if len(scope.Recipients) == 0 && len(scope.Additional) == 0 {
logger.Error("Batch Send Email",
logger.Field("message", "No recipients or additional emails provided"),
logger.Field("task_id", w.id),
)
return
}
var content task.EmailContent
if err := json.Unmarshal([]byte(taskInfo.Content), &content); err != nil {
logger.Error("Batch Send Email",
logger.Field("message", "Failed to parse task content"),
logger.Field("error", err.Error()),
logger.Field("task_id", w.id),
)
return
}
w.status = 1 // 设置状态为运行中
var recipients []string
// 解析收件人
if len(scope.Recipients) > 0 {
recipients = append(recipients, scope.Recipients...)
}
// 解析附加收件人
if len(scope.Additional) > 0 {
recipients = append(recipients, scope.Additional...)
}
// 去重和清理空字符串
recipients = tool.RemoveDuplicateElements(recipients...)
if len(recipients) == 0 {
logger.Error("Batch Send Email",
logger.Field("message", "No valid recipients found"),
logger.Field("task_id", w.id),
)
w.status = 2 // 设置状态为已完成
return
}
// 设置发送间隔时间
var intervalTime time.Duration
if scope.Interval == 0 {
intervalTime = 1 * time.Second
} else {
intervalTime = time.Duration(scope.Interval) * time.Second
}
var errors []ErrorInfo
var count uint64
for _, recipient := range recipients {
select {
case <-w.ctx.Done():
logger.Info("Batch Send Email",
logger.Field("message", "Worker stopped by context cancellation"),
logger.Field("task_id", w.id),
)
return
default:
}
if taskInfo.Status == 0 {
taskInfo.Status = 1 // 1 表示任务进行中
}
if err := w.sender.Send([]string{recipient}, content.Subject, content.Content); err != nil {
logger.Error("Batch Send Email",
logger.Field("message", "Failed to send email"),
logger.Field("error", err.Error()),
logger.Field("recipient", recipient),
logger.Field("task_id", w.id),
)
errors = append(errors, ErrorInfo{
Error: err.Error(),
Email: recipient,
Time: time.Now().Unix(),
})
text, _ := json.Marshal(errors)
taskInfo.Errors = string(text)
}
count++
taskInfo.Current = count
if err := tx.Model(&task.Task{}).Where("`id` = ?", taskInfo.Id).Save(&taskInfo).Error; err != nil {
logger.Error("Batch Send Email",
logger.Field("message", "Failed to update task progress"),
logger.Field("error", err.Error()),
logger.Field("task_id", w.id),
)
errors = append(errors, ErrorInfo{
Error: err.Error(),
Email: recipient,
Time: time.Now().Unix(),
})
w.status = 2 // 设置状态为已完成
}
time.Sleep(intervalTime)
}
taskInfo.Status = 2 // 2 表示任务已完成
w.status = 2 // 设置状态为已完成
if err := tx.Model(&task.Task{}).Where("`id` = ?", taskInfo.Id).Save(&taskInfo).Error; err != nil {
logger.Error("Batch Send Email",
logger.Field("message", "Failed to finalize task"),
logger.Field("error", err.Error()),
logger.Field("task_id", w.id),
)
} else {
logger.Info("Batch Send Email",
logger.Field("message", "Task completed successfully"),
logger.Field("task_id", w.id),
logger.Field("total_sent", count),
)
}
}