feat: 优化监控仪表盘 UI
- 添加 appbar 导航栏,支持 Chart/Queues 视图切换 - appbar 切换使用 history API,支持浏览器前进/后退 - 图表视图占满整个可视区域 - queue-modal 共享 appbar 样式 - 修复 queue tab count 字段名大小写问题 - tooltip 跟随鼠标显示在右下方,移除箭头 - 图表 canvas 鼠标样式改为准星 - pause/resume 队列后刷新列表 - example 添加 flag 配置参数
This commit is contained in:
114
example/main.go
114
example/main.go
@@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
@@ -11,7 +12,12 @@ import (
|
||||
"time"
|
||||
|
||||
"code.tczkiot.com/wlw/taskq"
|
||||
"code.tczkiot.com/wlw/taskq/x/inspector"
|
||||
"code.tczkiot.com/wlw/taskq/x/metrics"
|
||||
"code.tczkiot.com/wlw/taskq/x/monitor"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
@@ -28,83 +34,99 @@ type ImageResizeTask struct {
|
||||
// 定义任务处理器
|
||||
func handleEmailTask(ctx context.Context, t EmailTask) error {
|
||||
log.Printf("处理邮件任务: 用户ID=%d, 模板ID=%s", t.UserID, t.TemplateID)
|
||||
// 模拟邮件发送逻辑
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleImageResizeTask(ctx context.Context, t ImageResizeTask) error {
|
||||
log.Printf("处理图片调整任务: 源URL=%s", t.SourceURL)
|
||||
// 模拟图片调整逻辑
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
redisAddr = flag.String("redis", "127.0.0.1:6379", "Redis 地址")
|
||||
redisDB = flag.Int("redis-db", 1, "Redis 数据库")
|
||||
httpAddr = flag.String("http", ":8081", "HTTP 服务地址")
|
||||
dbPath = flag.String("db", "./taskq_stats.db", "SQLite 数据库路径")
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
// 创建 Redis 客户端
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: "127.0.0.1:6379",
|
||||
DB: 1,
|
||||
Addr: *redisAddr,
|
||||
DB: *redisDB,
|
||||
})
|
||||
defer rdb.Close()
|
||||
|
||||
// 初始化 taskq
|
||||
taskq.SetRedis(rdb)
|
||||
taskq.Init()
|
||||
|
||||
// 创建邮件任务
|
||||
emailTask := &taskq.Task[EmailTask]{
|
||||
emailTask := &taskq.Task{
|
||||
Queue: "email",
|
||||
Name: "email:deliver",
|
||||
MaxRetries: 3,
|
||||
Priority: 5,
|
||||
TTR: 0,
|
||||
Handler: handleEmailTask,
|
||||
}
|
||||
|
||||
// 创建图片调整任务
|
||||
imageTask := &taskq.Task[ImageResizeTask]{
|
||||
imageTask := &taskq.Task{
|
||||
Queue: "image",
|
||||
Name: "image:resize",
|
||||
MaxRetries: 3,
|
||||
Priority: 3,
|
||||
TTR: 0,
|
||||
Handler: handleImageResizeTask,
|
||||
}
|
||||
|
||||
// 注册任务
|
||||
if err := taskq.Register(emailTask); err != nil {
|
||||
log.Fatal("注册邮件任务失败:", err)
|
||||
}
|
||||
if err := taskq.Register(imageTask); err != nil {
|
||||
log.Fatal("注册图片任务失败:", err)
|
||||
// 创建 Inspector 插件(用于监控仪表盘)
|
||||
ins := inspector.New(inspector.Options{
|
||||
Interval: 2 * time.Second,
|
||||
DBPath: *dbPath,
|
||||
})
|
||||
|
||||
// 创建 Metrics 插件(用于 Prometheus)
|
||||
met := metrics.New(metrics.Options{
|
||||
Namespace: "taskq",
|
||||
Interval: 15 * time.Second,
|
||||
})
|
||||
|
||||
// 配置 taskq
|
||||
if err := taskq.Configure(taskq.Config{
|
||||
Redis: rdb,
|
||||
Tasks: []*taskq.Task{emailTask, imageTask},
|
||||
Plugins: []taskq.Plugin{ins, met},
|
||||
}); err != nil {
|
||||
log.Fatal("配置 taskq 失败:", err)
|
||||
}
|
||||
|
||||
// 创建监控 HTTP 处理器
|
||||
handler, err := taskq.NewHTTPHandler(taskq.HTTPHandlerOptions{
|
||||
RootPath: "/monitor",
|
||||
ReadOnly: false,
|
||||
// 创建监控服务
|
||||
servlet := taskq.Default()
|
||||
mon, err := monitor.New(monitor.Options{
|
||||
Inspector: ins,
|
||||
Queues: servlet.Queues(),
|
||||
RootPath: "/monitor",
|
||||
ReadOnly: false,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal("创建监控处理器失败:", err)
|
||||
log.Fatal("创建监控服务失败:", err)
|
||||
}
|
||||
|
||||
// 创建可取消的 context
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
// 启动 taskq 服务器(包含统计采集器)
|
||||
go func() {
|
||||
err := taskq.Start(ctx, taskq.StartOptions{
|
||||
StatsInterval: 2 * time.Second,
|
||||
StatsDBPath: "./taskq_stats.db",
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal("启动 taskq 服务器失败:", err)
|
||||
}
|
||||
}()
|
||||
// 初始化 taskq(初始化所有插件)
|
||||
if err := taskq.Init(ctx); err != nil {
|
||||
log.Fatal("初始化 taskq 失败:", err)
|
||||
}
|
||||
|
||||
// 启动 taskq 服务器(启动所有插件)
|
||||
if err := taskq.Start(ctx); err != nil {
|
||||
log.Fatal("启动 taskq 服务器失败:", err)
|
||||
}
|
||||
|
||||
// 定时发布任务
|
||||
go func() {
|
||||
ticker := time.NewTicker(5 * time.Second) // 每5秒发布一次任务
|
||||
ticker := time.NewTicker(5 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
taskCounter := 0
|
||||
@@ -162,15 +184,22 @@ func main() {
|
||||
}
|
||||
}()
|
||||
|
||||
// 创建 HTTP 路由
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/monitor/", mon)
|
||||
mux.Handle("/metrics", promhttp.Handler())
|
||||
|
||||
// 创建 HTTP 服务器
|
||||
server := &http.Server{
|
||||
Addr: ":8081",
|
||||
Handler: handler,
|
||||
Addr: *httpAddr,
|
||||
Handler: mux,
|
||||
}
|
||||
|
||||
// 启动 HTTP 服务器(非阻塞)
|
||||
// 启动 HTTP 服务器
|
||||
go func() {
|
||||
log.Printf("启动监控服务器在 http://localhost:8081")
|
||||
log.Printf("启动服务器在 http://localhost%s", *httpAddr)
|
||||
log.Printf(" - 监控仪表盘: http://localhost%s/monitor", *httpAddr)
|
||||
log.Printf(" - Prometheus: http://localhost%s/metrics", *httpAddr)
|
||||
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
log.Fatal("HTTP 服务器错误:", err)
|
||||
}
|
||||
@@ -186,10 +215,10 @@ func main() {
|
||||
// 1. 取消 context,停止任务发布
|
||||
cancel()
|
||||
|
||||
// 2. 关闭监控 HTTP 处理器(会断开 SSE 连接)
|
||||
handler.Close()
|
||||
// 2. 关闭监控服务(断开 SSE 连接)
|
||||
mon.Close()
|
||||
|
||||
// 3. 关闭 HTTP 服务器(设置 5 秒超时)
|
||||
// 3. 关闭 HTTP 服务器
|
||||
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer shutdownCancel()
|
||||
|
||||
@@ -197,9 +226,8 @@ func main() {
|
||||
log.Printf("HTTP 服务器关闭错误: %v", err)
|
||||
}
|
||||
|
||||
// 4. 停止 taskq 服务器(会等待完全关闭)
|
||||
// 4. 停止 taskq 服务器(会自动调用插件的 OnStop)
|
||||
taskq.Stop()
|
||||
|
||||
log.Println("服务已安全关闭")
|
||||
// rdb.Close() 由 defer 执行
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user