Files
membank/auto-check/pkg/report/template.go
张威33321 700ba91883 重构 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 行纯编排,所有业务逻辑下沉各包
2026-08-11 20:58:19 +08:00

162 lines
3.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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()
}