Files
tyapi-server/internal/infrastructure/http/handlers/qygl_report_handler.go
2026-03-11 19:36:26 +08:00

206 lines
6.6 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 handlers
import (
"encoding/json"
"fmt"
"html/template"
"net/http"
"net/url"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"tyapi-server/internal/application/api/commands"
"tyapi-server/internal/domains/api/dto"
api_repositories "tyapi-server/internal/domains/api/repositories"
api_services "tyapi-server/internal/domains/api/services"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/domains/api/services/processors/qygl"
"tyapi-server/internal/shared/pdf"
)
// QYGLReportHandler 企业全景报告页面渲染处理器
// 使用 QYGLJ1U9 聚合接口生成企业报告数据,并通过模板引擎渲染 qiye.html
type QYGLReportHandler struct {
apiRequestService *api_services.ApiRequestService
logger *zap.Logger
reportRepo api_repositories.ReportRepository
pdfCacheManager *pdf.PDFCacheManager
}
// NewQYGLReportHandler 创建企业报告页面处理器
func NewQYGLReportHandler(
apiRequestService *api_services.ApiRequestService,
logger *zap.Logger,
reportRepo api_repositories.ReportRepository,
pdfCacheManager *pdf.PDFCacheManager,
) *QYGLReportHandler {
return &QYGLReportHandler{
apiRequestService: apiRequestService,
logger: logger,
reportRepo: reportRepo,
pdfCacheManager: pdfCacheManager,
}
}
// GetQYGLReportPage 企业全景报告页面
// GET /reports/qygl?ent_code=xxx&ent_name=yyy&ent_reg_no=zzz
func (h *QYGLReportHandler) GetQYGLReportPage(c *gin.Context) {
// 读取查询参数
entCode := c.Query("ent_code")
entName := c.Query("ent_name")
entRegNo := c.Query("ent_reg_no")
// 组装 QYGLUY3S 入参
req := dto.QYGLUY3SReq{
EntName: entName,
EntRegno: entRegNo,
EntCode: entCode,
}
params, err := json.Marshal(req)
if err != nil {
h.logger.Error("序列化企业全景报告入参失败", zap.Error(err))
c.String(http.StatusInternalServerError, "生成企业报告失败,请稍后重试")
return
}
// 通过 ApiRequestService 调用 QYGLJ1U9 聚合处理器
options := &commands.ApiCallOptions{}
callCtx := &processors.CallContext{}
ctx := c.Request.Context()
respBytes, err := h.apiRequestService.PreprocessRequestApi(ctx, "QYGLJ1U9", params, options, callCtx)
if err != nil {
h.logger.Error("调用企业全景报告处理器失败", zap.Error(err))
c.String(http.StatusInternalServerError, "生成企业报告失败,请稍后重试")
return
}
var report map[string]interface{}
if err := json.Unmarshal(respBytes, &report); err != nil {
h.logger.Error("解析企业全景报告结果失败", zap.Error(err))
c.String(http.StatusInternalServerError, "生成企业报告失败,请稍后重试")
return
}
reportJSONBytes, err := json.Marshal(report)
if err != nil {
h.logger.Error("序列化企业全景报告结果失败", zap.Error(err))
c.String(http.StatusInternalServerError, "生成企业报告失败,请稍后重试")
return
}
// 使用 template.JS 避免在脚本中被转义,直接作为 JS 对象字面量注入
reportJSON := template.JS(reportJSONBytes)
c.HTML(http.StatusOK, "qiye.html", gin.H{
"ReportJSON": reportJSON,
})
}
// GetQYGLReportPageByID 通过编号查看企业全景报告页面
// GET /reports/qygl/:id
func (h *QYGLReportHandler) GetQYGLReportPageByID(c *gin.Context) {
id := c.Param("id")
if id == "" {
c.String(http.StatusBadRequest, "报告编号不能为空")
return
}
// 优先从数据库中查询报告记录
if h.reportRepo != nil {
if entity, err := h.reportRepo.FindByReportID(c.Request.Context(), id); err == nil && entity != nil {
reportJSON := template.JS(entity.ReportData)
c.HTML(http.StatusOK, "qiye.html", gin.H{
"ReportJSON": reportJSON,
})
return
}
}
// 回退到进程内存缓存(兼容老的访问方式)
report, ok := qygl.GetQYGLReport(id)
if !ok {
c.String(http.StatusNotFound, "报告不存在或已过期")
return
}
reportJSONBytes, err := json.Marshal(report)
if err != nil {
h.logger.Error("序列化企业全景报告结果失败", zap.Error(err))
c.String(http.StatusInternalServerError, "生成企业报告失败,请稍后再试")
return
}
reportJSON := template.JS(reportJSONBytes)
c.HTML(http.StatusOK, "qiye.html", gin.H{
"ReportJSON": reportJSON,
})
}
// GetQYGLReportPDFByID 通过编号导出企业全景报告 PDF基于 headless Chrome 渲染 HTML
// GET /reports/qygl/:id/pdf
func (h *QYGLReportHandler) GetQYGLReportPDFByID(c *gin.Context) {
id := c.Param("id")
if id == "" {
c.String(http.StatusBadRequest, "报告编号不能为空")
return
}
// 可选:从数据库查一次,用于生成更友好的文件名
var fileName = "企业全景报告.pdf"
if h.reportRepo != nil {
if entity, err := h.reportRepo.FindByReportID(c.Request.Context(), id); err == nil && entity != nil {
if entity.EntName != "" {
fileName = fmt.Sprintf("%s_企业全景报告.pdf", entity.EntName)
}
}
}
// 临时关闭企业全景报告 PDF 的读取缓存,强制每次都重新生成
// 如果后续需要恢复缓存,可在此重新启用 pdfCacheManager.GetByReportID 的逻辑。
// 根据当前请求推断访问协议(支持通过反向代理的 X-Forwarded-Proto
scheme := "http"
if c.Request.TLS != nil {
scheme = "https"
} else if forwardedProto := c.Request.Header.Get("X-Forwarded-Proto"); forwardedProto != "" {
scheme = forwardedProto
}
// 构建用于 headless 浏览器访问的完整报告页面 URL
reportURL := fmt.Sprintf("%s://%s/reports/qygl/%s", scheme, c.Request.Host, id)
h.logger.Info("开始生成企业全景报告 PDFheadless Chrome",
zap.String("report_id", id),
zap.String("url", reportURL),
)
pdfGen := pdf.NewHTMLPDFGenerator(h.logger)
pdfBytes, err := pdfGen.GenerateFromURL(c.Request.Context(), reportURL)
if err != nil {
h.logger.Error("生成企业全景报告 PDF 失败", zap.String("report_id", id), zap.Error(err))
c.String(http.StatusInternalServerError, "生成企业报告 PDF 失败,请稍后重试")
return
}
if len(pdfBytes) == 0 {
h.logger.Error("生成的企业全景报告 PDF 为空", zap.String("report_id", id))
c.String(http.StatusInternalServerError, "生成的企业报告 PDF 为空,请稍后重试")
return
}
// 临时关闭企业全景报告 PDF 的写入缓存,只返回最新生成的 PDF。
// 若后续需要重新启用缓存,可恢复对 pdfCacheManager.SetByReportID 的调用。
encodedFileName := url.QueryEscape(fileName)
c.Header("Content-Type", "application/pdf")
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", encodedFileName))
c.Data(http.StatusOK, "application/pdf", pdfBytes)
}