This commit is contained in:
2026-04-21 22:36:48 +08:00
commit 488c695fdf
748 changed files with 266838 additions and 0 deletions

135
cmd/api/main.go Normal file
View 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"
}

View 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)
}

View 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)
}

View 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)
}
}