feat: 后端限速实现 + 前端i18n全面国际化 + 安全与代码质量修复
后端: - trafficmgr: 实现 tc htb 限速(ApplyRateLimit/RemoveRateLimit),自动推导接口名 - 各协议服务: 代码质量改进与错误处理增强 前端: - protocols.js: 170个硬编码中文字符串全部转为 i18n key,中英文双语翻译 - 提取 fmtBytes() 到 utils/format.js 公共模块(消除 4 处重复) - router.js: 添加路由级权限守卫,非管理员无法直接 URL 访问管理页 - Tunnels.vue: SSE 日志流改用 fetch+ReadableStream,token 放 Authorization header - VPNUsers.vue: 修复 catch 块中文硬编码 fallback - 新增 TaskProgress 组件、taskmgr 后端任务管理
This commit is contained in:
1 parent
021d012478
commit
91974fc30f
26 files changed
+1977
-377
No files matched your search
@@ -0,0 +1,232 @@
|
||||
// Package taskmgr 提供后台并发任务管理器
|
||||
package taskmgr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TaskStatus 任务状态
|
||||
type TaskStatus string
|
||||
|
||||
const (
|
||||
StatusPending TaskStatus = "pending"
|
||||
StatusRunning TaskStatus = "running"
|
||||
StatusDone TaskStatus = "done"
|
||||
StatusFailed TaskStatus = "failed"
|
||||
StatusCanceled TaskStatus = "canceled"
|
||||
)
|
||||
|
||||
// TaskAction 任务动作
|
||||
type TaskAction string
|
||||
|
||||
const (
|
||||
ActionStart TaskAction = "start"
|
||||
ActionStop TaskAction = "stop"
|
||||
ActionDelete TaskAction = "delete"
|
||||
)
|
||||
|
||||
// SubTask 单个子任务(对应一个隧道)
|
||||
type SubTask struct {
|
||||
TunnelID uint `json:"tunnel_id"`
|
||||
TunnelName string `json:"tunnel_name"`
|
||||
Status TaskStatus `json:"status"`
|
||||
Error string `json:"error,omitempty"`
|
||||
StartedAt *time.Time `json:"started_at,omitempty"`
|
||||
FinishedAt *time.Time `json:"finished_at,omitempty"`
|
||||
}
|
||||
|
||||
// Task 一个批量任务
|
||||
type Task struct {
|
||||
ID string `json:"id"`
|
||||
Action TaskAction `json:"action"`
|
||||
Status TaskStatus `json:"status"`
|
||||
Total int `json:"total"`
|
||||
Success int `json:"success"`
|
||||
Failed int `json:"failed"`
|
||||
SubTasks []SubTask `json:"sub_tasks"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// Progress 返回进度百分比 0-100
|
||||
func (t *Task) Progress() int {
|
||||
if t.Total == 0 {
|
||||
return 0
|
||||
}
|
||||
done := t.Success + t.Failed
|
||||
return done * 100 / t.Total
|
||||
}
|
||||
|
||||
// Manager 内存任务管理器
|
||||
type Manager struct {
|
||||
mu sync.RWMutex
|
||||
tasks map[string]*Task
|
||||
seq int
|
||||
}
|
||||
|
||||
func NewManager() *Manager {
|
||||
return &Manager{tasks: make(map[string]*Task)}
|
||||
}
|
||||
|
||||
// Create 创建任务并返回 ID
|
||||
func (m *Manager) Create(action TaskAction, tunnelIDs []uint, names map[uint]string) string {
|
||||
m.mu.Lock()
|
||||
m.seq++
|
||||
id := fmt.Sprintf("task_%d_%d", time.Now().UnixMilli(), m.seq)
|
||||
subTasks := make([]SubTask, len(tunnelIDs))
|
||||
for i, tid := range tunnelIDs {
|
||||
subTasks[i] = SubTask{
|
||||
TunnelID: tid,
|
||||
TunnelName: names[tid],
|
||||
Status: StatusPending,
|
||||
}
|
||||
}
|
||||
t := &Task{
|
||||
ID: id,
|
||||
Action: action,
|
||||
Status: StatusPending,
|
||||
Total: len(tunnelIDs),
|
||||
SubTasks: subTasks,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
m.tasks[id] = t
|
||||
m.mu.Unlock()
|
||||
|
||||
// 自动清理 30 分钟前的任务
|
||||
go m.cleanup()
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
// Get 获取任务快照
|
||||
func (m *Manager) Get(id string) (*Task, bool) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
t, ok := m.tasks[id]
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
// 返回副本
|
||||
cp := *t
|
||||
cp.SubTasks = make([]SubTask, len(t.SubTasks))
|
||||
copy(cp.SubTasks, t.SubTasks)
|
||||
return &cp, true
|
||||
}
|
||||
|
||||
// List 列出最近的任务(最多 50 个)
|
||||
func (m *Manager) List() []*Task {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
out := make([]*Task, 0, len(m.tasks))
|
||||
for _, t := range m.tasks {
|
||||
cp := *t
|
||||
cp.SubTasks = make([]SubTask, len(t.SubTasks))
|
||||
copy(cp.SubTasks, t.SubTasks)
|
||||
out = append(out, &cp)
|
||||
}
|
||||
// 按创建时间倒序
|
||||
for i := 0; i < len(out)-1; i++ {
|
||||
for j := i + 1; j < len(out); j++ {
|
||||
if out[j].CreatedAt.After(out[i].CreatedAt) {
|
||||
out[i], out[j] = out[j], out[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(out) > 50 {
|
||||
out = out[:50]
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// MarkRunning 标记子任务运行中
|
||||
func (m *Manager) MarkRunning(taskID string, tunnelID uint) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
t, ok := m.tasks[taskID]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
t.Status = StatusRunning
|
||||
now := time.Now()
|
||||
t.UpdatedAt = now
|
||||
for i := range t.SubTasks {
|
||||
if t.SubTasks[i].TunnelID == tunnelID {
|
||||
t.SubTasks[i].Status = StatusRunning
|
||||
t.SubTasks[i].StartedAt = &now
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MarkDone 标记子任务成功
|
||||
func (m *Manager) MarkDone(taskID string, tunnelID uint) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
t, ok := m.tasks[taskID]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
t.UpdatedAt = now
|
||||
for i := range t.SubTasks {
|
||||
if t.SubTasks[i].TunnelID == tunnelID {
|
||||
t.SubTasks[i].Status = StatusDone
|
||||
t.SubTasks[i].FinishedAt = &now
|
||||
t.Success++
|
||||
break
|
||||
}
|
||||
}
|
||||
t.maybeDone()
|
||||
}
|
||||
|
||||
// MarkFailed 标记子任务失败
|
||||
func (m *Manager) MarkFailed(taskID string, tunnelID uint, err error) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
t, ok := m.tasks[taskID]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
t.UpdatedAt = now
|
||||
for i := range t.SubTasks {
|
||||
if t.SubTasks[i].TunnelID == tunnelID {
|
||||
t.SubTasks[i].Status = StatusFailed
|
||||
t.SubTasks[i].Error = err.Error()
|
||||
t.SubTasks[i].FinishedAt = &now
|
||||
t.Failed++
|
||||
break
|
||||
}
|
||||
}
|
||||
t.maybeDone()
|
||||
}
|
||||
|
||||
// maybeDone 检查任务是否全部完成(调用者需持锁)
|
||||
func (t *Task) maybeDone() {
|
||||
if t.Success+t.Failed >= t.Total {
|
||||
if t.Failed > 0 {
|
||||
t.Status = StatusDone // 部分失败也算完成
|
||||
} else {
|
||||
t.Status = StatusDone
|
||||
}
|
||||
now := time.Now()
|
||||
t.UpdatedAt = now
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) cleanup() {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
cutoff := time.Now().Add(-30 * time.Minute)
|
||||
for id, t := range m.tasks {
|
||||
if t.Status == StatusDone || t.Status == StatusFailed || t.Status == StatusCanceled {
|
||||
if t.UpdatedAt.Before(cutoff) {
|
||||
delete(m.tasks, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user