Files
tyapi-server/cmd/qygl_report_preview/main.go
2026-03-21 19:10:50 +08:00

160 lines
4.8 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.

// 仅读取 build 后的报告 JSON本地渲染 qiye.html不执行 BuildReportFromRawSources
//
// go run ./cmd/qygl_report_preview -in resources/dev-report/built.json
// go run ./cmd/qygl_report_preview -in built.json -addr :8899 -watch
//
// 每次打开/刷新页面都会重新读取 -in 文件;加 -watch 后保存 JSON 会自动刷新浏览器。
package main
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"html/template"
"log"
"net/http"
"os"
"path/filepath"
)
func parseBuiltReport(data []byte) (map[string]interface{}, error) {
var root map[string]interface{}
if err := json.Unmarshal(data, &root); err != nil {
return nil, err
}
if _, ok := root["jiguangFull"]; ok {
return nil, fmt.Errorf("检测到 raw 字段 jiguangFull请先执行: go run ./cmd/qygl_report_build -in <raw.json> -out built.json")
}
if k, _ := root["kind"].(string); k == "full" {
r, ok := root["report"].(map[string]interface{})
if !ok {
return nil, fmt.Errorf("kind=full 时缺少 report 对象")
}
return r, nil
}
if r, ok := root["report"].(map[string]interface{}); ok {
return r, nil
}
if root["entName"] != nil || root["basic"] != nil || root["reportTime"] != nil {
return root, nil
}
return nil, fmt.Errorf("不是有效的 build 后报告(根级应有 entName、basic、reportTime 之一,或 {\"report\":{...}} / kind=full")
}
func fileVersionTag(path string) (string, error) {
st, err := os.Stat(path)
if err != nil {
return "", err
}
return fmt.Sprintf("%d-%d", st.ModTime().UnixNano(), st.Size()), nil
}
func renderPage(tmpl *template.Template, report map[string]interface{}, injectLive bool) ([]byte, error) {
reportBytes, err := json.Marshal(report)
if err != nil {
return nil, err
}
var buf bytes.Buffer
if err := tmpl.Execute(&buf, map[string]interface{}{
"ReportJSON": template.JS(reportBytes),
}); err != nil {
return nil, err
}
b := buf.Bytes()
if !injectLive {
return b, nil
}
script := `<script>(function(){var v0=null;function tick(){fetch("/__version?="+Date.now(),{cache:"no-store"}).then(function(r){return r.text();}).then(function(v){if(v==="")return;if(v0===null)v0=v;else if(v0!==v){v0=v;location.reload();}}).catch(function(){});}setInterval(tick,600);tick();})();</script>`
closing := []byte("</body>")
idx := bytes.LastIndex(b, closing)
if idx < 0 {
return append(b, []byte(script)...), nil
}
out := make([]byte, 0, len(b)+len(script))
out = append(out, b[:idx]...)
out = append(out, script...)
out = append(out, b[idx:]...)
return out, nil
}
func main() {
addr := flag.String("addr", ":8899", "监听地址")
root := flag.String("root", ".", "项目根目录(含 resources/qiye.html")
inPath := flag.String("in", "", "build 后的 JSON由 qygl_report_build 生成,或 fixture.full 中的 report 形态)")
watch := flag.Bool("watch", false, "监听 -in 文件变化并自动刷新浏览器(轮询)")
flag.Parse()
if *inPath == "" {
log.Fatal("请指定 -in <built.json>")
}
rootAbs, err := filepath.Abs(*root)
if err != nil {
log.Fatalf("解析 root: %v", err)
}
tplPath := filepath.Join(rootAbs, "resources", "qiye.html")
if _, err := os.Stat(tplPath); err != nil {
log.Fatalf("未找到模板 %s: %v", tplPath, err)
}
var inAbs string
if filepath.IsAbs(*inPath) {
inAbs = *inPath
} else {
inAbs = filepath.Join(rootAbs, *inPath)
}
if _, err := os.Stat(inAbs); err != nil {
log.Fatalf("读取 %s: %v", inAbs, err)
}
tmpl, err := template.ParseFiles(tplPath)
if err != nil {
log.Fatalf("解析模板: %v", err)
}
http.HandleFunc("/__version", func(w http.ResponseWriter, r *http.Request) {
tag, err := fileVersionTag(inAbs)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
w.Header().Set("Cache-Control", "no-store")
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
_, _ = w.Write([]byte(tag))
})
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
raw, err := os.ReadFile(inAbs)
if err != nil {
http.Error(w, "读取报告文件失败: "+err.Error(), http.StatusInternalServerError)
return
}
report, err := parseBuiltReport(raw)
if err != nil {
http.Error(w, "解析 JSON 失败: "+err.Error(), http.StatusInternalServerError)
return
}
html, err := renderPage(tmpl, report, *watch)
if err != nil {
http.Error(w, "渲染失败: "+err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
_, _ = w.Write(html)
})
log.Printf("报告预览: http://127.0.0.1%s/ (每请求重读 %s", *addr, inAbs)
if *watch {
log.Printf("已启用 -watch保存 JSON 后约 0.6s 内自动刷新页面")
}
if err := http.ListenAndServe(*addr, nil); err != nil {
log.Fatal(err)
}
}