f
This commit is contained in:
77
cmd/qygl_report_build/main.go
Normal file
77
cmd/qygl_report_build/main.go
Normal file
@@ -0,0 +1,77 @@
|
||||
// 将 raw fixture(各处理器原始 JSON)经 BuildReportFromRawSources 转化为与线上一致的完整报告 JSON。
|
||||
//
|
||||
// go run ./cmd/qygl_report_build -in resources/dev-report/fixture.raw.example.json -out resources/dev-report/built.json
|
||||
// go run ./cmd/qygl_report_build -in raw.json -out - # 输出到 stdout
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"tyapi-server/internal/domains/api/services/processors/qygl"
|
||||
)
|
||||
|
||||
type rawBundle struct {
|
||||
Kind string `json:"kind"`
|
||||
|
||||
JiguangFull map[string]interface{} `json:"jiguangFull"`
|
||||
JudicialCertFull map[string]interface{} `json:"judicialCertFull"`
|
||||
EquityPanorama map[string]interface{} `json:"equityPanorama"`
|
||||
AnnualReport map[string]interface{} `json:"annualReport"`
|
||||
TaxViolation map[string]interface{} `json:"taxViolation"`
|
||||
TaxArrears map[string]interface{} `json:"taxArrears"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
inPath := flag.String("in", "", "raw fixture JSON 路径(含 jiguangFull 等字段,可参考 fixture.raw.example.json)")
|
||||
outPath := flag.String("out", "", "输出文件;- 或留空表示输出到 stdout")
|
||||
flag.Parse()
|
||||
|
||||
if *inPath == "" {
|
||||
log.Fatal("请指定 -in <raw.json>")
|
||||
}
|
||||
raw, err := os.ReadFile(*inPath)
|
||||
if err != nil {
|
||||
log.Fatalf("读取输入失败: %v", err)
|
||||
}
|
||||
|
||||
var b rawBundle
|
||||
if err := json.Unmarshal(raw, &b); err != nil {
|
||||
log.Fatalf("解析 JSON 失败: %v", err)
|
||||
}
|
||||
|
||||
if b.Kind == "full" {
|
||||
log.Fatal("输入为 kind=full(已是 build 结果),无需再转化;预览请用: go run ./cmd/qygl_report_preview")
|
||||
}
|
||||
if b.Kind != "" && b.Kind != "raw" {
|
||||
log.Fatalf("若填写 kind,仅支持 raw,当前: %q", b.Kind)
|
||||
}
|
||||
|
||||
report := qygl.BuildReportFromRawSources(
|
||||
b.JiguangFull,
|
||||
b.JudicialCertFull,
|
||||
b.EquityPanorama,
|
||||
b.AnnualReport,
|
||||
b.TaxViolation,
|
||||
b.TaxArrears,
|
||||
)
|
||||
|
||||
out, err := json.MarshalIndent(report, "", " ")
|
||||
if err != nil {
|
||||
log.Fatalf("序列化报告失败: %v", err)
|
||||
}
|
||||
|
||||
if *outPath == "" || *outPath == "-" {
|
||||
if _, err := os.Stdout.Write(append(out, '\n')); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err := os.WriteFile(*outPath, append(out, '\n'), 0644); err != nil {
|
||||
log.Fatalf("写入失败: %v", err)
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "已写入 %s\n", *outPath)
|
||||
}
|
||||
159
cmd/qygl_report_preview/main.go
Normal file
159
cmd/qygl_report_preview/main.go
Normal file
@@ -0,0 +1,159 @@
|
||||
// 仅读取 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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user