f
This commit is contained in:
135
cmd/api/main.go
Normal file
135
cmd/api/main.go
Normal file
@@ -0,0 +1,135 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"hyapi-server/internal/app"
|
||||
)
|
||||
|
||||
// @title HYAPI Server API
|
||||
// @version 1.0
|
||||
// @description 基于DDD和Clean Architecture的企业级后端API服务
|
||||
// @description 采用Gin框架构建,支持用户管理、JWT认证、事件驱动等功能
|
||||
|
||||
// @contact.name API Support
|
||||
// @contact.url https://github.com/your-org/hyapi-server
|
||||
// @contact.email support@example.com
|
||||
|
||||
// @license.name Apache 2.0
|
||||
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
// @host localhost:8080
|
||||
// @BasePath /
|
||||
|
||||
// @securityDefinitions.apikey Bearer
|
||||
// @in header
|
||||
// @name Authorization
|
||||
// @description Type "Bearer" followed by a space and JWT token.
|
||||
|
||||
// 构建时注入的变量
|
||||
var (
|
||||
version = "dev"
|
||||
commit = "none"
|
||||
date = "unknown"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 设置时区为北京时间
|
||||
time.Local = time.FixedZone("CST", 8*3600)
|
||||
|
||||
// 命令行参数
|
||||
var (
|
||||
showVersion = flag.Bool("version", false, "显示版本信息")
|
||||
migrate = flag.Bool("migrate", false, "运行数据库迁移")
|
||||
health = flag.Bool("health", false, "执行健康检查")
|
||||
env = flag.String("env", "", "指定运行环境 (development|production|testing)")
|
||||
)
|
||||
flag.Parse()
|
||||
|
||||
// 处理版本信息显示 (不需要初始化完整应用)
|
||||
if *showVersion {
|
||||
fmt.Printf("HYAPI Server\n")
|
||||
fmt.Printf("Version: %s\n", version)
|
||||
fmt.Printf("Commit: %s\n", commit)
|
||||
fmt.Printf("Build Date: %s\n", date)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// 设置环境变量(如果通过命令行指定)
|
||||
if *env != "" {
|
||||
if err := validateEnvironment(*env); err != nil {
|
||||
log.Fatalf("无效的环境参数: %v", err)
|
||||
}
|
||||
os.Setenv("ENV", *env)
|
||||
fmt.Printf("🌍 通过命令行设置环境: %s\n", *env)
|
||||
}
|
||||
|
||||
// 显示当前环境
|
||||
currentEnv := getCurrentEnvironment()
|
||||
fmt.Printf("🔧 当前运行环境: %s\n", currentEnv)
|
||||
|
||||
// 生产环境安全提示
|
||||
if currentEnv == "production" {
|
||||
fmt.Printf("⚠️ 生产环境模式 - 请确保配置正确\n")
|
||||
}
|
||||
|
||||
// 创建应用程序实例
|
||||
application, err := app.NewApplication()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create application: %v", err)
|
||||
}
|
||||
|
||||
// 处理命令行参数
|
||||
if *migrate {
|
||||
fmt.Println("Running database migrations...")
|
||||
if err := application.RunCommand("migrate"); err != nil {
|
||||
log.Fatalf("Migration failed: %v", err)
|
||||
}
|
||||
fmt.Println("Database migrations completed successfully")
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if *health {
|
||||
fmt.Println("Performing health check...")
|
||||
if err := application.RunCommand("health"); err != nil {
|
||||
log.Fatalf("Health check failed: %v", err)
|
||||
}
|
||||
fmt.Println("Health check passed")
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// 启动应用程序 (使用完整的架构)
|
||||
fmt.Printf("🚀 Starting HYAPI Server v%s (%s)\n", version, commit)
|
||||
if err := application.Run(); err != nil {
|
||||
log.Fatalf("Application failed to start: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// validateEnvironment 验证环境参数
|
||||
func validateEnvironment(env string) error {
|
||||
validEnvs := []string{"development", "production", "testing"}
|
||||
for _, validEnv := range validEnvs {
|
||||
if env == validEnv {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("环境必须是以下之一: %v", validEnvs)
|
||||
}
|
||||
|
||||
// getCurrentEnvironment 获取当前环境(与config包中的逻辑保持一致)
|
||||
func getCurrentEnvironment() string {
|
||||
if env := os.Getenv("CONFIG_ENV"); env != "" {
|
||||
return env
|
||||
}
|
||||
if env := os.Getenv("ENV"); env != "" {
|
||||
return env
|
||||
}
|
||||
if env := os.Getenv("APP_ENV"); env != "" {
|
||||
return env
|
||||
}
|
||||
return "development"
|
||||
}
|
||||
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"
|
||||
|
||||
"hyapi-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)
|
||||
}
|
||||
59
cmd/qygl_report_pdf/main.go
Normal file
59
cmd/qygl_report_pdf/main.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"hyapi-server/internal/shared/pdf"
|
||||
)
|
||||
|
||||
// 一个本地调试用的小工具:
|
||||
// 从 JSON 文件(企业报告.json)读取 QYGL 聚合结果,使用 gofpdf 生成企业全景报告 PDF,输出到当前目录。
|
||||
func main() {
|
||||
var (
|
||||
jsonPath string
|
||||
outPath string
|
||||
)
|
||||
flag.StringVar(&jsonPath, "json", "企业报告.json", "企业报告 JSON 数据源文件路径")
|
||||
flag.StringVar(&outPath, "out", "企业全景报告_gofpdf.pdf", "输出 PDF 文件路径")
|
||||
flag.Parse()
|
||||
|
||||
logger, _ := zap.NewDevelopment()
|
||||
defer logger.Sync()
|
||||
|
||||
absJSON, _ := filepath.Abs(jsonPath)
|
||||
fmt.Printf("读取 JSON 数据源:%s\n", absJSON)
|
||||
|
||||
data, err := os.ReadFile(jsonPath)
|
||||
if err != nil {
|
||||
fmt.Printf("读取 JSON 文件失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var report map[string]interface{}
|
||||
if err := json.Unmarshal(data, &report); err != nil {
|
||||
fmt.Printf("解析 JSON 失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Println("开始使用 gofpdf 生成企业全景报告 PDF...")
|
||||
pdfBytes, err := pdf.GenerateQYGLReportPDF(context.Background(), logger, report)
|
||||
if err != nil {
|
||||
fmt.Printf("生成 PDF 失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(outPath, pdfBytes, 0644); err != nil {
|
||||
fmt.Printf("写入 PDF 文件失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
absOut, _ := filepath.Abs(outPath)
|
||||
fmt.Printf("PDF 生成完成:%s\n", absOut)
|
||||
}
|
||||
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