重构 auto-check:拆分包架构,精简 workflow 为纯调度循环
各包职责: - config: 分类 YAML 配置 - model: Device/IP/MAC 公共类型 - discovery: 扫描 + ARP MAC 解析 + Devices 变量 - sshclient: SSH 连接 - stress: stress-ng/stressapptest 压测 + Results 变量 - report: 80mm 热敏模板/sparkline/打印 + Printed 变量 - workflow: 永久循环调度 + Start/Stop workflow 已精简为 100 行纯编排,所有业务逻辑下沉各包
This commit is contained in:
161
auto-check/pkg/report/template.go
Normal file
161
auto-check/pkg/report/template.go
Normal file
@@ -0,0 +1,161 @@
|
||||
package report
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ============================
|
||||
// 模板渲染(80mm 热敏纸适配)
|
||||
// ============================
|
||||
|
||||
// TemplateConfig 模板渲染配置
|
||||
type TemplateConfig struct {
|
||||
Width int // 行宽(字符数),80mm 热敏纸建议 42
|
||||
}
|
||||
|
||||
// DefaultTemplateConfig 默认配置(80mm 热敏纸)
|
||||
func DefaultTemplateConfig() TemplateConfig {
|
||||
return TemplateConfig{Width: 42}
|
||||
}
|
||||
|
||||
// RenderTemplate 使用 Go template 渲染报告
|
||||
// tpl 为模板内容,data 为报告数据
|
||||
func RenderTemplate(tpl string, data interface{}, cfg TemplateConfig) (string, error) {
|
||||
if cfg.Width <= 0 {
|
||||
cfg.Width = 42
|
||||
}
|
||||
|
||||
funcMap := template.FuncMap{
|
||||
// 格式化辅助
|
||||
"timeFmt": func(t time.Time) string { return t.Format("2006-01-02 15:04:05") },
|
||||
// 字符串截断/填充到固定宽度
|
||||
"trunc": func(s string, n int) string {
|
||||
r := []rune(s)
|
||||
if len(r) <= n {
|
||||
return string(r)
|
||||
}
|
||||
return string(r[:n-1]) + "…"
|
||||
},
|
||||
// 左侧填充(右对齐)
|
||||
"padRight": func(s string, n int) string {
|
||||
r := []rune(s)
|
||||
if len(r) >= n {
|
||||
return string(r[:n])
|
||||
}
|
||||
return s + strings.Repeat(" ", n-len(r))
|
||||
},
|
||||
// 分隔线
|
||||
"hr": func(sharp rune) string { return strings.Repeat(string(sharp), cfg.Width) },
|
||||
// 曲线图(时间序列 → sparkline)
|
||||
"sparkline": func(data []float64, width int) string {
|
||||
return Sparkline(data, width)
|
||||
},
|
||||
// 曲线摘要: 最高/最低/平均
|
||||
"curveSummary": func(data []float64) string {
|
||||
if len(data) == 0 {
|
||||
return "无数据"
|
||||
}
|
||||
min, max := math.MaxFloat64, -math.MaxFloat64
|
||||
var sum float64
|
||||
for _, v := range data {
|
||||
if v < min {
|
||||
min = v
|
||||
}
|
||||
if v > max {
|
||||
max = v
|
||||
}
|
||||
sum += v
|
||||
}
|
||||
avg := sum / float64(len(data))
|
||||
return fmt.Sprintf("max=%.1f min=%.1f avg=%.1f", max, min, avg)
|
||||
},
|
||||
}
|
||||
|
||||
t, err := template.New("report").Funcs(funcMap).Parse(tpl)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("解析模板失败: %w", err)
|
||||
}
|
||||
|
||||
var sb strings.Builder
|
||||
if err := t.Execute(&sb, data); err != nil {
|
||||
return "", fmt.Errorf("渲染模板失败: %w", err)
|
||||
}
|
||||
return sb.String(), nil
|
||||
}
|
||||
|
||||
// ============================
|
||||
// Sparkline — 80mm 热敏纸曲线图
|
||||
// ============================
|
||||
|
||||
// sparkChars 8 级块状字符(低→高)
|
||||
var sparkChars = []rune("▁▂▃▄▅▆▇█")
|
||||
|
||||
// Sparkline 将时间序列渲染为一行块状曲线
|
||||
// width 为字符宽度(80mm 纸 ≤42)
|
||||
func Sparkline(data []float64, width int) string {
|
||||
if len(data) == 0 {
|
||||
return "(无数据)"
|
||||
}
|
||||
if width <= 0 {
|
||||
width = 42
|
||||
}
|
||||
if width > 42 {
|
||||
width = 42
|
||||
}
|
||||
|
||||
// 单点
|
||||
if len(data) == 1 {
|
||||
return fmt.Sprintf("▊ %.1f", data[0])
|
||||
}
|
||||
|
||||
// 按宽度采样(超出宽度时等距抽取)
|
||||
step := float64(len(data)) / float64(width)
|
||||
sampled := make([]float64, 0, width)
|
||||
for i := 0; i < width && int(float64(i)*step) < len(data); i++ {
|
||||
idx := int(float64(i) * step)
|
||||
if idx >= len(data) {
|
||||
break
|
||||
}
|
||||
sampled = append(sampled, data[idx])
|
||||
}
|
||||
|
||||
// 归一化到 0-7
|
||||
min, max := math.MaxFloat64, -math.MaxFloat64
|
||||
for _, v := range sampled {
|
||||
if v < min {
|
||||
min = v
|
||||
}
|
||||
if v > max {
|
||||
max = v
|
||||
}
|
||||
}
|
||||
|
||||
var sb strings.Builder
|
||||
if max-min < 1e-9 {
|
||||
// 全相同值
|
||||
idx := 0
|
||||
if max > 0 {
|
||||
idx = 3
|
||||
}
|
||||
for range sampled {
|
||||
sb.WriteRune(sparkChars[idx])
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
for _, v := range sampled {
|
||||
idx := int((v - min) / (max - min) * 7)
|
||||
if idx < 0 {
|
||||
idx = 0
|
||||
}
|
||||
if idx > 7 {
|
||||
idx = 7
|
||||
}
|
||||
sb.WriteRune(sparkChars[idx])
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
Reference in New Issue
Block a user