From 2fea046981f2374c08b1af0326a29a5af22c4638 Mon Sep 17 00:00:00 2001 From: liangzai <2440983361@qq.com> Date: Thu, 29 Jan 2026 15:03:38 +0800 Subject: [PATCH 1/9] f --- docker-compose.prod.yml | 16 -------- .../processors/pdfg/pdfg01gz_processor.go | 41 +++++++++++-------- .../external/pdfgen/pdfgen_service.go | 8 +--- .../http/handlers/pdfg_handler.go | 23 ++++------- internal/shared/pdf/pdf_cache_manager.go | 7 ++++ 5 files changed, 42 insertions(+), 53 deletions(-) diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 813358e..6def26b 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -105,14 +105,6 @@ services: retries: 5 start_period: 60s restart: unless-stopped - deploy: - resources: - limits: - memory: 1G - cpus: "1.0" - reservations: - memory: 256M - cpus: "0.3" # TYAPI Worker 服务 tyapi-worker: @@ -147,14 +139,6 @@ services: retries: 5 start_period: 60s restart: unless-stopped - deploy: - resources: - limits: - memory: 512M - cpus: "0.5" - reservations: - memory: 128M - cpus: "0.1" # Asynq 任务监控 (生产环境) asynq-monitor: diff --git a/internal/domains/api/services/processors/pdfg/pdfg01gz_processor.go b/internal/domains/api/services/processors/pdfg/pdfg01gz_processor.go index a247aec..b287a8f 100644 --- a/internal/domains/api/services/processors/pdfg/pdfg01gz_processor.go +++ b/internal/domains/api/services/processors/pdfg/pdfg01gz_processor.go @@ -31,8 +31,8 @@ func ProcessPDFG01GZRequest(ctx context.Context, params []byte, deps *processors // 获取全局logger zapLogger := logger.GetGlobalLogger() - // Debug:记录入口参数 - zapLogger.Debug("PDFG01GZ请求开始", + // Debug:记录入口参数(使用 Info 级别便于线上查看) + logger.L().Info("PDFG01GZ请求开始", zap.String("name", paramsDto.Name), zap.String("id_card", paramsDto.IDCard), zap.String("mobile_no", paramsDto.MobileNo), @@ -56,8 +56,8 @@ func ProcessPDFG01GZRequest(ctx context.Context, params []byte, deps *processors if cfg, ok := ctx.Value("config").(*config.Config); ok && cfg != nil { maxSize = cfg.PDFGen.Cache.MaxSize - // Debug:记录PDF生成服务配置 - zapLogger.Debug("PDFG01GZ加载配置", + // Debug:记录PDF生成服务配置(使用 Info 级别便于线上查看) + logger.L().Info("PDFG01GZ加载配置", zap.String("env", cfg.App.Env), zap.String("pdfgen_production_url", cfg.PDFGen.ProductionURL), zap.String("pdfgen_development_url", cfg.PDFGen.DevelopmentURL), @@ -93,24 +93,29 @@ func ProcessPDFG01GZRequest(ctx context.Context, params []byte, deps *processors pdfGenService = pdfgen.NewPDFGenService(defaultCfg, zapLogger) } - // 检查缓存 + // 检查缓存(基于姓名+身份证) _, hit, createdAt, err := cacheManager.Get(paramsDto.Name, paramsDto.IDCard) if err != nil { zapLogger.Warn("检查缓存失败,继续生成PDF", zap.Error(err)) } else if hit { + // 计算缓存键,作为报告ID(可持久化) + reportID := cacheManager.GetCacheKey(paramsDto.Name, paramsDto.IDCard) + // 缓存命中,模拟慢几秒 zapLogger.Info("PDF缓存命中,返回缓存文件", zap.String("name", paramsDto.Name), zap.String("id_card", paramsDto.IDCard), + zap.String("report_id", reportID), zap.Time("created_at", createdAt), ) // 模拟慢几秒(2-4秒) time.Sleep(2 * time.Second) - // 生成下载链接 - downloadURL := generateDownloadURL(paramsDto.Name, paramsDto.IDCard) + // 生成下载链接(基于报告ID) + downloadURL := generateDownloadURL(reportID) return json.Marshal(map[string]interface{}{ "download_url": downloadURL, + "report_id": reportID, "cached": true, "created_at": createdAt.Format(time.RFC3339), }) @@ -130,7 +135,7 @@ func ProcessPDFG01GZRequest(ctx context.Context, params []byte, deps *processors // 打印完整的apiData(为避免日志过大,这里直接序列化为JSON字符串) apiDataBytes, _ := json.Marshal(apiData) - zapLogger.Debug("PDFG01GZ数据准备完成", + logger.L().Info("PDFG01GZ数据准备完成", zap.Int("api_data_count", len(apiData)), zap.Int("formatted_items", len(formattedData)), zap.ByteString("api_data", apiDataBytes), @@ -150,6 +155,9 @@ func ProcessPDFG01GZRequest(ctx context.Context, params []byte, deps *processors reportNumber = generateReportNumber() } + // 计算报告ID(与缓存键一致,便于通过ID直接下载) + reportID := cacheManager.GetCacheKey(paramsDto.Name, paramsDto.IDCard) + // 构建PDF生成请求 pdfReq := &pdfgen.GeneratePDFRequest{ Data: formattedData, @@ -159,11 +167,10 @@ func ProcessPDFG01GZRequest(ctx context.Context, params []byte, deps *processors // 调用PDF生成服务 // 即使部分子处理器失败,只要有APPLICANT_BASIC_INFO就可以生成PDF - zapLogger.Debug("PDFG01GZ开始调用PDF生成服务", + logger.L().Info("PDFG01GZ开始调用PDF生成服务", zap.String("report_number", reportNumber), zap.Int("data_items", len(formattedData)), ) - pdfResp, err := pdfGenService.GenerateGuangzhouPDF(ctx, pdfReq) if err != nil { zapLogger.Error("生成PDF失败", @@ -173,24 +180,26 @@ func ProcessPDFG01GZRequest(ctx context.Context, params []byte, deps *processors return nil, errors.Join(processors.ErrSystem, fmt.Errorf("生成PDF失败: %w", err)) } - // 保存到缓存 + // 保存到缓存(基于姓名+身份证) if err := cacheManager.Set(paramsDto.Name, paramsDto.IDCard, pdfResp.PDFBytes); err != nil { zapLogger.Warn("保存PDF到缓存失败", zap.Error(err)) // 不影响返回结果,只记录警告 } - // 生成下载链接 - downloadURL := generateDownloadURL(paramsDto.Name, paramsDto.IDCard) + // 生成下载链接(基于报告ID) + downloadURL := generateDownloadURL(reportID) zapLogger.Info("PDF生成成功", zap.String("name", paramsDto.Name), zap.String("id_card", paramsDto.IDCard), + zap.String("report_id", reportID), zap.String("report_number", reportNumber), zap.String("download_url", downloadURL), ) return json.Marshal(map[string]interface{}{ "download_url": downloadURL, + "report_id": reportID, "report_number": reportNumber, "cached": false, }) @@ -491,10 +500,10 @@ func generateReportNumber() string { return fmt.Sprintf("RPT%s", time.Now().Format("20060102150405")) } -// generateDownloadURL 生成下载链接 -func generateDownloadURL(name, idCard string) string { +// generateDownloadURL 生成下载链接(基于报告ID/缓存键) +func generateDownloadURL(reportID string) string { // 这里应该生成实际的下载URL // 暂时返回一个占位符,实际应该根据服务器配置生成 - return fmt.Sprintf("/api/v1/pdfg/download?name=%s&id_card=%s", name, idCard) + return fmt.Sprintf("/api/v1/pdfg/download?id=%s", reportID) } diff --git a/internal/infrastructure/external/pdfgen/pdfgen_service.go b/internal/infrastructure/external/pdfgen/pdfgen_service.go index ba21aac..a5b1c0a 100644 --- a/internal/infrastructure/external/pdfgen/pdfgen_service.go +++ b/internal/infrastructure/external/pdfgen/pdfgen_service.go @@ -93,12 +93,6 @@ func (s *PDFGenService) GenerateGuangzhouPDF(ctx context.Context, req *GenerateP return nil, fmt.Errorf("序列化请求失败: %w", err) } - // Debug:打印请求体预览(最多1024字节),防止日志过大 - bodyPreview := reqBody - if len(bodyPreview) > 1024 { - bodyPreview = bodyPreview[:1024] - } - // 构建请求URL url := fmt.Sprintf("%s%s", s.baseURL, s.apiPath) @@ -117,7 +111,7 @@ func (s *PDFGenService) GenerateGuangzhouPDF(ctx context.Context, req *GenerateP s.logger.Info("开始调用PDF生成服务", zap.String("url", url), zap.Int("data_count", len(req.Data)), - zap.ByteString("body_preview", bodyPreview), + zap.ByteString("reqBody", reqBody), ) resp, err := s.client.Do(httpReq) diff --git a/internal/infrastructure/http/handlers/pdfg_handler.go b/internal/infrastructure/http/handlers/pdfg_handler.go index fb4740d..2f3f1b1 100644 --- a/internal/infrastructure/http/handlers/pdfg_handler.go +++ b/internal/infrastructure/http/handlers/pdfg_handler.go @@ -33,22 +33,20 @@ func NewPDFGHandler( } // DownloadPDF 下载PDF文件 -// GET /api/v1/pdfg/download?name=xxx&id_card=xxx +// GET /api/v1/pdfg/download?id=报告ID func (h *PDFGHandler) DownloadPDF(c *gin.Context) { - name := c.Query("name") - idCard := c.Query("id_card") + reportID := c.Query("id") - if name == "" || idCard == "" { - h.responseBuilder.BadRequest(c, "姓名和身份证号不能为空") + if reportID == "" { + h.responseBuilder.BadRequest(c, "报告ID不能为空") return } // 从缓存获取PDF - pdfBytes, hit, createdAt, err := h.cacheManager.Get(name, idCard) + pdfBytes, hit, createdAt, err := h.cacheManager.GetByCacheKey(reportID) if err != nil { h.logger.Error("获取PDF缓存失败", - zap.String("name", name), - zap.String("id_card", idCard), + zap.String("report_id", reportID), zap.Error(err), ) h.responseBuilder.InternalError(c, "获取PDF文件失败") @@ -57,8 +55,7 @@ func (h *PDFGHandler) DownloadPDF(c *gin.Context) { if !hit { h.logger.Warn("PDF文件不存在或已过期", - zap.String("name", name), - zap.String("id_card", idCard), + zap.String("report_id", reportID), ) h.responseBuilder.NotFound(c, "PDF文件不存在或已过期,请重新生成") return @@ -68,8 +65,7 @@ func (h *PDFGHandler) DownloadPDF(c *gin.Context) { expiresAt := createdAt.Add(24 * time.Hour) if time.Now().After(expiresAt) { h.logger.Warn("PDF文件已过期", - zap.String("name", name), - zap.String("id_card", idCard), + zap.String("report_id", reportID), zap.Time("expires_at", expiresAt), ) h.responseBuilder.NotFound(c, "PDF文件已过期,请重新生成") @@ -85,8 +81,7 @@ func (h *PDFGHandler) DownloadPDF(c *gin.Context) { c.Data(http.StatusOK, "application/pdf", pdfBytes) h.logger.Info("PDF文件下载成功", - zap.String("name", name), - zap.String("id_card", idCard), + zap.String("report_id", reportID), zap.Int("file_size", len(pdfBytes)), ) } diff --git a/internal/shared/pdf/pdf_cache_manager.go b/internal/shared/pdf/pdf_cache_manager.go index 4874838..c4b0aa7 100644 --- a/internal/shared/pdf/pdf_cache_manager.go +++ b/internal/shared/pdf/pdf_cache_manager.go @@ -98,6 +98,13 @@ func (m *PDFCacheManager) GetByProduct(productID, version string) ([]byte, bool, return pdfBytes, hit, err } +// GetByCacheKey 通过缓存键直接获取PDF文件 +// 适用于已经持久化了缓存键(例如作为报告ID)的场景 +// 返回PDF字节流、是否命中缓存、文件创建时间、错误 +func (m *PDFCacheManager) GetByCacheKey(cacheKey string) ([]byte, bool, time.Time, error) { + return m.getByKey(cacheKey, "", "") +} + // getByKey 内部方法:根据缓存键获取文件 func (m *PDFCacheManager) getByKey(cacheKey string, key1, key2 string) ([]byte, bool, time.Time, error) { cachePath := m.GetCachePath(cacheKey) From 88787c61459abe804d16ed4a2e349212beb3bfad Mon Sep 17 00:00:00 2001 From: liangzai <2440983361@qq.com> Date: Thu, 29 Jan 2026 15:25:30 +0800 Subject: [PATCH 2/9] f --- internal/domains/api/dto/pdfg_dto.go | 1 - .../processors/pdfg/pdfg01gz_processor.go | 31 +++++++------------ 2 files changed, 12 insertions(+), 20 deletions(-) diff --git a/internal/domains/api/dto/pdfg_dto.go b/internal/domains/api/dto/pdfg_dto.go index a351c70..8d97b4e 100644 --- a/internal/domains/api/dto/pdfg_dto.go +++ b/internal/domains/api/dto/pdfg_dto.go @@ -5,7 +5,6 @@ type PDFG01GZReq struct { Name string `json:"name" validate:"required,min=1,validName"` IDCard string `json:"id_card" validate:"required,validIDCard"` MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"` - AuthAuthorizeFileCode string `json:"auth_authorize_file_code" validate:"required"` // IVYZ5A9O需要 Authorized string `json:"authorized" validate:"required,oneof=0 1"` // 授权标识,0或1 } diff --git a/internal/domains/api/services/processors/pdfg/pdfg01gz_processor.go b/internal/domains/api/services/processors/pdfg/pdfg01gz_processor.go index b287a8f..8ca35d7 100644 --- a/internal/domains/api/services/processors/pdfg/pdfg01gz_processor.go +++ b/internal/domains/api/services/processors/pdfg/pdfg01gz_processor.go @@ -37,7 +37,6 @@ func ProcessPDFG01GZRequest(ctx context.Context, params []byte, deps *processors zap.String("id_card", paramsDto.IDCard), zap.String("mobile_no", paramsDto.MobileNo), zap.String("authorized", paramsDto.Authorized), - zap.String("auth_authorize_file_code", paramsDto.AuthAuthorizeFileCode), ) // 从context获取config(如果存在) @@ -219,35 +218,29 @@ func collectAPIData(ctx context.Context, params dto.PDFG01GZReq, deps *processor results := make(chan processorResult, 5) - // 调用IVYZ5A9O - 需要: name, id_card, auth_authorize_file_code + // 调用JRZQ0L85 - 需要: name, id_card, mobile_no go func() { defer func() { if r := recover(); r != nil { - logger.Error("调用IVYZ5A9O处理器时发生panic", + logger.Error("调用JRZQ0L85处理器时发生panic", zap.Any("panic", r), ) - results <- processorResult{"IVYZ5A9O", nil, fmt.Errorf("处理器panic: %v", r)} + results <- processorResult{"JRZQ0L85", nil, fmt.Errorf("处理器panic: %v", r)} } }() - // 检查必需字段 - if params.AuthAuthorizeFileCode == "" { - logger.Warn("IVYZ5A9O缺少auth_authorize_file_code字段,跳过调用") - results <- processorResult{"IVYZ5A9O", nil, fmt.Errorf("缺少必需字段: auth_authorize_file_code")} - return + jrzq0l85Params := map[string]interface{}{ + "name": params.Name, + "id_card": params.IDCard, + "mobileNo": params.MobileNo, } - ivyzParams := map[string]interface{}{ - "name": params.Name, - "id_card": params.IDCard, - "auth_authorize_file_code": params.AuthAuthorizeFileCode, - } - paramsBytes, err := json.Marshal(ivyzParams) + paramsBytes, err := json.Marshal(jrzq0l85Params) if err != nil { - logger.Warn("序列化IVYZ5A9O参数失败", zap.Error(err)) - results <- processorResult{"IVYZ5A9O", nil, err} + logger.Warn("序列化JRZQ0L85参数失败", zap.Error(err)) + results <- processorResult{"JRZQ0L85", nil, err} return } - data, err := callProcessor(ctx, "IVYZ5A9O", paramsBytes, deps) - results <- processorResult{"IVYZ5A9O", data, err} + data, err := callProcessor(ctx, "JRZQ0L85", paramsBytes, deps) + results <- processorResult{"JRZQ0L85", data, err} }() // 调用JRZQ8A2D - 需要: name, id_card, mobile_no, authorized From 360fed39078c32d31227741cc9ce9a22e7b25510 Mon Sep 17 00:00:00 2001 From: liangzai <2440983361@qq.com> Date: Thu, 29 Jan 2026 15:27:45 +0800 Subject: [PATCH 3/9] f --- .../processors/pdfg/pdfg01gz_processor.go | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/internal/domains/api/services/processors/pdfg/pdfg01gz_processor.go b/internal/domains/api/services/processors/pdfg/pdfg01gz_processor.go index 8ca35d7..490b524 100644 --- a/internal/domains/api/services/processors/pdfg/pdfg01gz_processor.go +++ b/internal/domains/api/services/processors/pdfg/pdfg01gz_processor.go @@ -42,12 +42,14 @@ func ProcessPDFG01GZRequest(ctx context.Context, params []byte, deps *processors // 从context获取config(如果存在) var cacheTTL time.Duration = 24 * time.Hour var cacheDir string + var apiDomain string if cfg, ok := ctx.Value("config").(*config.Config); ok && cfg != nil { cacheTTL = cfg.PDFGen.Cache.TTL if cacheTTL == 0 { cacheTTL = 24 * time.Hour } cacheDir = cfg.PDFGen.Cache.CacheDir + apiDomain = cfg.API.Domain } // 获取最大缓存大小 @@ -111,12 +113,15 @@ func ProcessPDFG01GZRequest(ctx context.Context, params []byte, deps *processors time.Sleep(2 * time.Second) // 生成下载链接(基于报告ID) - downloadURL := generateDownloadURL(reportID) + downloadURL := generateDownloadURL(apiDomain, reportID) + expiresAt := createdAt.Add(cacheTTL) return json.Marshal(map[string]interface{}{ "download_url": downloadURL, "report_id": reportID, "cached": true, "created_at": createdAt.Format(time.RFC3339), + "expires_at": expiresAt.Format(time.RFC3339), + "ttl_seconds": int(cacheTTL.Seconds()), }) } @@ -186,7 +191,8 @@ func ProcessPDFG01GZRequest(ctx context.Context, params []byte, deps *processors } // 生成下载链接(基于报告ID) - downloadURL := generateDownloadURL(reportID) + downloadURL := generateDownloadURL(apiDomain, reportID) + expiresAt := time.Now().Add(cacheTTL) zapLogger.Info("PDF生成成功", zap.String("name", paramsDto.Name), @@ -201,6 +207,8 @@ func ProcessPDFG01GZRequest(ctx context.Context, params []byte, deps *processors "report_id": reportID, "report_number": reportNumber, "cached": false, + "expires_at": expiresAt.Format(time.RFC3339), + "ttl_seconds": int(cacheTTL.Seconds()), }) } @@ -494,9 +502,13 @@ func generateReportNumber() string { } // generateDownloadURL 生成下载链接(基于报告ID/缓存键) -func generateDownloadURL(reportID string) string { - // 这里应该生成实际的下载URL - // 暂时返回一个占位符,实际应该根据服务器配置生成 - return fmt.Sprintf("/api/v1/pdfg/download?id=%s", reportID) +// apiDomain: 外部可访问的API域名,如 api.tianyuanapi.com +func generateDownloadURL(apiDomain, reportID string) string { + if apiDomain == "" { + // 兜底:保留相对路径,方便本地/测试环境使用 + return fmt.Sprintf("/api/v1/pdfg/download?id=%s", reportID) + } + // 生成完整链接,例如:https://api.tianyuanapi.com/api/v1/pdfg/download?id=xxx + return fmt.Sprintf("https://%s/api/v1/pdfg/download?id=%s", apiDomain, reportID) } From 167dd63a7a1ddce2b018a1f5dbc9c6e35e52f5fb Mon Sep 17 00:00:00 2001 From: liangzai <2440983361@qq.com> Date: Thu, 29 Jan 2026 15:35:25 +0800 Subject: [PATCH 4/9] f --- .../api/services/processors/pdfg/pdfg01gz_processor.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/internal/domains/api/services/processors/pdfg/pdfg01gz_processor.go b/internal/domains/api/services/processors/pdfg/pdfg01gz_processor.go index 490b524..efa9b97 100644 --- a/internal/domains/api/services/processors/pdfg/pdfg01gz_processor.go +++ b/internal/domains/api/services/processors/pdfg/pdfg01gz_processor.go @@ -102,15 +102,15 @@ func ProcessPDFG01GZRequest(ctx context.Context, params []byte, deps *processors // 计算缓存键,作为报告ID(可持久化) reportID := cacheManager.GetCacheKey(paramsDto.Name, paramsDto.IDCard) - // 缓存命中,模拟慢几秒 + // 缓存命中,模拟生成耗时(约4秒) zapLogger.Info("PDF缓存命中,返回缓存文件", zap.String("name", paramsDto.Name), zap.String("id_card", paramsDto.IDCard), zap.String("report_id", reportID), zap.Time("created_at", createdAt), ) - // 模拟慢几秒(2-4秒) - time.Sleep(2 * time.Second) + // 模拟生成耗时 + time.Sleep(4 * time.Second) // 生成下载链接(基于报告ID) downloadURL := generateDownloadURL(apiDomain, reportID) @@ -118,7 +118,6 @@ func ProcessPDFG01GZRequest(ctx context.Context, params []byte, deps *processors return json.Marshal(map[string]interface{}{ "download_url": downloadURL, "report_id": reportID, - "cached": true, "created_at": createdAt.Format(time.RFC3339), "expires_at": expiresAt.Format(time.RFC3339), "ttl_seconds": int(cacheTTL.Seconds()), @@ -206,7 +205,6 @@ func ProcessPDFG01GZRequest(ctx context.Context, params []byte, deps *processors "download_url": downloadURL, "report_id": reportID, "report_number": reportNumber, - "cached": false, "expires_at": expiresAt.Format(time.RFC3339), "ttl_seconds": int(cacheTTL.Seconds()), }) From 2325a110b61cb55245126afe633231b84f983e7f Mon Sep 17 00:00:00 2001 From: liangzai <2440983361@qq.com> Date: Thu, 29 Jan 2026 15:48:07 +0800 Subject: [PATCH 5/9] f --- docker-compose.prod.yml | 2 ++ .../processors/pdfg/pdfg01gz_processor.go | 20 ++++++++--- .../http/handlers/pdfg_handler.go | 34 ++++++++++++++----- 3 files changed, 43 insertions(+), 13 deletions(-) diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 6def26b..8b4dba1 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -90,6 +90,8 @@ services: volumes: - ./logs:/app/logs - ./resources/Pure_Component:/app/resources/Pure_Component + # 持久化PDF缓存目录,确保生成的PDF在容器重启后仍然存在 + - ./storage/pdfg-cache:/app/storage/pdfg-cache # user: "1001:1001" # 注释掉,使用root权限运行 networks: - tyapi-network diff --git a/internal/domains/api/services/processors/pdfg/pdfg01gz_processor.go b/internal/domains/api/services/processors/pdfg/pdfg01gz_processor.go index efa9b97..75e2a55 100644 --- a/internal/domains/api/services/processors/pdfg/pdfg01gz_processor.go +++ b/internal/domains/api/services/processors/pdfg/pdfg01gz_processor.go @@ -94,13 +94,16 @@ func ProcessPDFG01GZRequest(ctx context.Context, params []byte, deps *processors pdfGenService = pdfgen.NewPDFGenService(defaultCfg, zapLogger) } + // 计算缓存键(内部使用,不直接暴露给用户) + cacheKey := cacheManager.GetCacheKey(paramsDto.Name, paramsDto.IDCard) + // 检查缓存(基于姓名+身份证) _, hit, createdAt, err := cacheManager.Get(paramsDto.Name, paramsDto.IDCard) if err != nil { zapLogger.Warn("检查缓存失败,继续生成PDF", zap.Error(err)) } else if hit { - // 计算缓存键,作为报告ID(可持久化) - reportID := cacheManager.GetCacheKey(paramsDto.Name, paramsDto.IDCard) + // 生成对外可见的报告ID(包含随机前缀,防止用户直接推断缓存键) + reportID := generateReportID(cacheKey) // 缓存命中,模拟生成耗时(约4秒) zapLogger.Info("PDF缓存命中,返回缓存文件", @@ -158,9 +161,6 @@ func ProcessPDFG01GZRequest(ctx context.Context, params []byte, deps *processors reportNumber = generateReportNumber() } - // 计算报告ID(与缓存键一致,便于通过ID直接下载) - reportID := cacheManager.GetCacheKey(paramsDto.Name, paramsDto.IDCard) - // 构建PDF生成请求 pdfReq := &pdfgen.GeneratePDFRequest{ Data: formattedData, @@ -189,6 +189,9 @@ func ProcessPDFG01GZRequest(ctx context.Context, params []byte, deps *processors // 不影响返回结果,只记录警告 } + // 生成报告ID(与内部缓存键解耦,对外只暴露带前缀的ID) + reportID := generateReportID(cacheKey) + // 生成下载链接(基于报告ID) downloadURL := generateDownloadURL(apiDomain, reportID) expiresAt := time.Now().Add(cacheTTL) @@ -499,6 +502,13 @@ func generateReportNumber() string { return fmt.Sprintf("RPT%s", time.Now().Format("20060102150405")) } +// generateReportID 生成对外可见的报告ID +// 结构:{report_number}-{cacheKey},前缀变化以避免用户轻易看出是否命中缓存 +func generateReportID(cacheKey string) string { + reportNumber := generateReportNumber() + return fmt.Sprintf("%s-%s", reportNumber, cacheKey) +} + // generateDownloadURL 生成下载链接(基于报告ID/缓存键) // apiDomain: 外部可访问的API域名,如 api.tianyuanapi.com func generateDownloadURL(apiDomain, reportID string) string { diff --git a/internal/infrastructure/http/handlers/pdfg_handler.go b/internal/infrastructure/http/handlers/pdfg_handler.go index 2f3f1b1..0376b05 100644 --- a/internal/infrastructure/http/handlers/pdfg_handler.go +++ b/internal/infrastructure/http/handlers/pdfg_handler.go @@ -3,6 +3,7 @@ package handlers import ( "fmt" "net/http" + "strings" "time" "github.com/gin-gonic/gin" @@ -35,18 +36,25 @@ func NewPDFGHandler( // DownloadPDF 下载PDF文件 // GET /api/v1/pdfg/download?id=报告ID func (h *PDFGHandler) DownloadPDF(c *gin.Context) { - reportID := c.Query("id") + rawReportID := c.Query("id") - if reportID == "" { + if rawReportID == "" { h.responseBuilder.BadRequest(c, "报告ID不能为空") return } + // 从对外报告ID中解析内部缓存键(使用最后一个'-'后的部分作为缓存键) + cacheKey := rawReportID + if idx := strings.LastIndex(rawReportID, "-"); idx > 0 && idx < len(rawReportID)-1 { + cacheKey = rawReportID[idx+1:] + } + // 从缓存获取PDF - pdfBytes, hit, createdAt, err := h.cacheManager.GetByCacheKey(reportID) + pdfBytes, hit, createdAt, err := h.cacheManager.GetByCacheKey(cacheKey) if err != nil { h.logger.Error("获取PDF缓存失败", - zap.String("report_id", reportID), + zap.String("report_id", rawReportID), + zap.String("cache_key", cacheKey), zap.Error(err), ) h.responseBuilder.InternalError(c, "获取PDF文件失败") @@ -55,7 +63,8 @@ func (h *PDFGHandler) DownloadPDF(c *gin.Context) { if !hit { h.logger.Warn("PDF文件不存在或已过期", - zap.String("report_id", reportID), + zap.String("report_id", rawReportID), + zap.String("cache_key", cacheKey), ) h.responseBuilder.NotFound(c, "PDF文件不存在或已过期,请重新生成") return @@ -65,7 +74,8 @@ func (h *PDFGHandler) DownloadPDF(c *gin.Context) { expiresAt := createdAt.Add(24 * time.Hour) if time.Now().After(expiresAt) { h.logger.Warn("PDF文件已过期", - zap.String("report_id", reportID), + zap.String("report_id", rawReportID), + zap.String("cache_key", cacheKey), zap.Time("expires_at", expiresAt), ) h.responseBuilder.NotFound(c, "PDF文件已过期,请重新生成") @@ -74,14 +84,22 @@ func (h *PDFGHandler) DownloadPDF(c *gin.Context) { // 设置响应头 c.Header("Content-Type", "application/pdf") - c.Header("Content-Disposition", `attachment; filename="大数据租赁风险报告.pdf"`) + // 使用报告ID前缀作为下载文件名的一部分,避免泄露内部缓存键 + filename := "大数据租赁风险报告.pdf" + if idx := strings.LastIndex(rawReportID, "-"); idx > 0 { + // 使用前缀(报告编号部分)作为文件名的一部分 + prefix := rawReportID[:idx] + filename = fmt.Sprintf("大数据租赁风险报告_%s.pdf", prefix) + } + c.Header("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, filename)) c.Header("Content-Length", fmt.Sprintf("%d", len(pdfBytes))) // 发送PDF文件 c.Data(http.StatusOK, "application/pdf", pdfBytes) h.logger.Info("PDF文件下载成功", - zap.String("report_id", reportID), + zap.String("report_id", rawReportID), + zap.String("cache_key", cacheKey), zap.Int("file_size", len(pdfBytes)), ) } From d1e06984ac23060ea6ee543ef111a47b400f8ec2 Mon Sep 17 00:00:00 2001 From: liangzai <2440983361@qq.com> Date: Thu, 29 Jan 2026 16:00:51 +0800 Subject: [PATCH 6/9] f --- .../domains/api/services/processors/pdfg/pdfg01gz_processor.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/domains/api/services/processors/pdfg/pdfg01gz_processor.go b/internal/domains/api/services/processors/pdfg/pdfg01gz_processor.go index 75e2a55..96c0f47 100644 --- a/internal/domains/api/services/processors/pdfg/pdfg01gz_processor.go +++ b/internal/domains/api/services/processors/pdfg/pdfg01gz_processor.go @@ -240,7 +240,7 @@ func collectAPIData(ctx context.Context, params dto.PDFG01GZReq, deps *processor jrzq0l85Params := map[string]interface{}{ "name": params.Name, "id_card": params.IDCard, - "mobileNo": params.MobileNo, + "mobile_no": params.MobileNo, } paramsBytes, err := json.Marshal(jrzq0l85Params) if err != nil { From f50e11a05208037d37a3b07985c018135ffe7f87 Mon Sep 17 00:00:00 2001 From: liangzai <2440983361@qq.com> Date: Thu, 29 Jan 2026 16:12:42 +0800 Subject: [PATCH 7/9] f --- .../processors/pdfg/pdfg01gz_processor.go | 34 ++----------------- 1 file changed, 2 insertions(+), 32 deletions(-) diff --git a/internal/domains/api/services/processors/pdfg/pdfg01gz_processor.go b/internal/domains/api/services/processors/pdfg/pdfg01gz_processor.go index 96c0f47..633bc1a 100644 --- a/internal/domains/api/services/processors/pdfg/pdfg01gz_processor.go +++ b/internal/domains/api/services/processors/pdfg/pdfg01gz_processor.go @@ -97,38 +97,8 @@ func ProcessPDFG01GZRequest(ctx context.Context, params []byte, deps *processors // 计算缓存键(内部使用,不直接暴露给用户) cacheKey := cacheManager.GetCacheKey(paramsDto.Name, paramsDto.IDCard) - // 检查缓存(基于姓名+身份证) - _, hit, createdAt, err := cacheManager.Get(paramsDto.Name, paramsDto.IDCard) - if err != nil { - zapLogger.Warn("检查缓存失败,继续生成PDF", zap.Error(err)) - } else if hit { - // 生成对外可见的报告ID(包含随机前缀,防止用户直接推断缓存键) - reportID := generateReportID(cacheKey) - - // 缓存命中,模拟生成耗时(约4秒) - zapLogger.Info("PDF缓存命中,返回缓存文件", - zap.String("name", paramsDto.Name), - zap.String("id_card", paramsDto.IDCard), - zap.String("report_id", reportID), - zap.Time("created_at", createdAt), - ) - // 模拟生成耗时 - time.Sleep(4 * time.Second) - - // 生成下载链接(基于报告ID) - downloadURL := generateDownloadURL(apiDomain, reportID) - expiresAt := createdAt.Add(cacheTTL) - return json.Marshal(map[string]interface{}{ - "download_url": downloadURL, - "report_id": reportID, - "created_at": createdAt.Format(time.RFC3339), - "expires_at": expiresAt.Format(time.RFC3339), - "ttl_seconds": int(cacheTTL.Seconds()), - }) - } - - // 缓存未命中,需要生成PDF - zapLogger.Info("PDF缓存未命中,开始生成PDF", + // 直接生成PDF,不检查缓存(每次都重新生成) + zapLogger.Info("开始生成PDF", zap.String("name", paramsDto.Name), zap.String("id_card", paramsDto.IDCard), ) From 5bff33547c7980bb0a4989c05857fc2b7f46290e Mon Sep 17 00:00:00 2001 From: liangzai <2440983361@qq.com> Date: Thu, 29 Jan 2026 16:34:37 +0800 Subject: [PATCH 8/9] f --- .../processors/pdfg/pdfg01gz_processor.go | 29 ++++++++++------- .../http/handlers/pdfg_handler.go | 32 +++++++------------ internal/shared/pdf/pdf_cache_manager.go | 22 +++++++++++++ 3 files changed, 51 insertions(+), 32 deletions(-) diff --git a/internal/domains/api/services/processors/pdfg/pdfg01gz_processor.go b/internal/domains/api/services/processors/pdfg/pdfg01gz_processor.go index 633bc1a..e0346bc 100644 --- a/internal/domains/api/services/processors/pdfg/pdfg01gz_processor.go +++ b/internal/domains/api/services/processors/pdfg/pdfg01gz_processor.go @@ -2,6 +2,8 @@ package pdfg import ( "context" + "crypto/rand" + "encoding/hex" "encoding/json" "errors" "fmt" @@ -94,9 +96,6 @@ func ProcessPDFG01GZRequest(ctx context.Context, params []byte, deps *processors pdfGenService = pdfgen.NewPDFGenService(defaultCfg, zapLogger) } - // 计算缓存键(内部使用,不直接暴露给用户) - cacheKey := cacheManager.GetCacheKey(paramsDto.Name, paramsDto.IDCard) - // 直接生成PDF,不检查缓存(每次都重新生成) zapLogger.Info("开始生成PDF", zap.String("name", paramsDto.Name), @@ -153,15 +152,15 @@ func ProcessPDFG01GZRequest(ctx context.Context, params []byte, deps *processors return nil, errors.Join(processors.ErrSystem, fmt.Errorf("生成PDF失败: %w", err)) } - // 保存到缓存(基于姓名+身份证) - if err := cacheManager.Set(paramsDto.Name, paramsDto.IDCard, pdfResp.PDFBytes); err != nil { + // 生成报告ID(每次请求都生成唯一的ID) + reportID := generateReportID() + + // 保存到缓存(基于报告ID,文件名包含时间戳确保唯一性) + if err := cacheManager.SetByReportID(reportID, pdfResp.PDFBytes); err != nil { zapLogger.Warn("保存PDF到缓存失败", zap.Error(err)) // 不影响返回结果,只记录警告 } - // 生成报告ID(与内部缓存键解耦,对外只暴露带前缀的ID) - reportID := generateReportID(cacheKey) - // 生成下载链接(基于报告ID) downloadURL := generateDownloadURL(apiDomain, reportID) expiresAt := time.Now().Add(cacheTTL) @@ -473,10 +472,18 @@ func generateReportNumber() string { } // generateReportID 生成对外可见的报告ID -// 结构:{report_number}-{cacheKey},前缀变化以避免用户轻易看出是否命中缓存 -func generateReportID(cacheKey string) string { +// 每次请求都生成唯一的ID,格式:{report_number}-{随机字符串} +// 注意:不再包含cacheKey,因为每次请求都会重新生成,不需要通过ID定位缓存文件 +func generateReportID() string { reportNumber := generateReportNumber() - return fmt.Sprintf("%s-%s", reportNumber, cacheKey) + // 生成8字节随机字符串,确保每次请求ID都不同 + randomBytes := make([]byte, 8) + if _, err := rand.Read(randomBytes); err != nil { + // 如果随机数生成失败,使用纳秒时间戳作为后备 + randomBytes = []byte(fmt.Sprintf("%d", time.Now().UnixNano())) + } + randomStr := hex.EncodeToString(randomBytes) + return fmt.Sprintf("%s-%s", reportNumber, randomStr) } // generateDownloadURL 生成下载链接(基于报告ID/缓存键) diff --git a/internal/infrastructure/http/handlers/pdfg_handler.go b/internal/infrastructure/http/handlers/pdfg_handler.go index 0376b05..dba25e3 100644 --- a/internal/infrastructure/http/handlers/pdfg_handler.go +++ b/internal/infrastructure/http/handlers/pdfg_handler.go @@ -36,25 +36,18 @@ func NewPDFGHandler( // DownloadPDF 下载PDF文件 // GET /api/v1/pdfg/download?id=报告ID func (h *PDFGHandler) DownloadPDF(c *gin.Context) { - rawReportID := c.Query("id") + reportID := c.Query("id") - if rawReportID == "" { + if reportID == "" { h.responseBuilder.BadRequest(c, "报告ID不能为空") return } - // 从对外报告ID中解析内部缓存键(使用最后一个'-'后的部分作为缓存键) - cacheKey := rawReportID - if idx := strings.LastIndex(rawReportID, "-"); idx > 0 && idx < len(rawReportID)-1 { - cacheKey = rawReportID[idx+1:] - } - - // 从缓存获取PDF - pdfBytes, hit, createdAt, err := h.cacheManager.GetByCacheKey(cacheKey) + // 通过报告ID获取PDF文件 + pdfBytes, hit, createdAt, err := h.cacheManager.GetByReportID(reportID) if err != nil { h.logger.Error("获取PDF缓存失败", - zap.String("report_id", rawReportID), - zap.String("cache_key", cacheKey), + zap.String("report_id", reportID), zap.Error(err), ) h.responseBuilder.InternalError(c, "获取PDF文件失败") @@ -63,8 +56,7 @@ func (h *PDFGHandler) DownloadPDF(c *gin.Context) { if !hit { h.logger.Warn("PDF文件不存在或已过期", - zap.String("report_id", rawReportID), - zap.String("cache_key", cacheKey), + zap.String("report_id", reportID), ) h.responseBuilder.NotFound(c, "PDF文件不存在或已过期,请重新生成") return @@ -74,8 +66,7 @@ func (h *PDFGHandler) DownloadPDF(c *gin.Context) { expiresAt := createdAt.Add(24 * time.Hour) if time.Now().After(expiresAt) { h.logger.Warn("PDF文件已过期", - zap.String("report_id", rawReportID), - zap.String("cache_key", cacheKey), + zap.String("report_id", reportID), zap.Time("expires_at", expiresAt), ) h.responseBuilder.NotFound(c, "PDF文件已过期,请重新生成") @@ -84,11 +75,11 @@ func (h *PDFGHandler) DownloadPDF(c *gin.Context) { // 设置响应头 c.Header("Content-Type", "application/pdf") - // 使用报告ID前缀作为下载文件名的一部分,避免泄露内部缓存键 + // 使用报告ID前缀作为下载文件名的一部分 filename := "大数据租赁风险报告.pdf" - if idx := strings.LastIndex(rawReportID, "-"); idx > 0 { + if idx := strings.LastIndex(reportID, "-"); idx > 0 { // 使用前缀(报告编号部分)作为文件名的一部分 - prefix := rawReportID[:idx] + prefix := reportID[:idx] filename = fmt.Sprintf("大数据租赁风险报告_%s.pdf", prefix) } c.Header("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, filename)) @@ -98,8 +89,7 @@ func (h *PDFGHandler) DownloadPDF(c *gin.Context) { c.Data(http.StatusOK, "application/pdf", pdfBytes) h.logger.Info("PDF文件下载成功", - zap.String("report_id", rawReportID), - zap.String("cache_key", cacheKey), + zap.String("report_id", reportID), zap.Int("file_size", len(pdfBytes)), ) } diff --git a/internal/shared/pdf/pdf_cache_manager.go b/internal/shared/pdf/pdf_cache_manager.go index c4b0aa7..bd671ce 100644 --- a/internal/shared/pdf/pdf_cache_manager.go +++ b/internal/shared/pdf/pdf_cache_manager.go @@ -78,6 +78,14 @@ func (m *PDFCacheManager) GetCacheKeyByProduct(productID, version string) string return hex.EncodeToString(hash[:]) } +// GetCacheKeyByReportID 生成缓存键(基于报告ID) +// 文件名格式:MD5(report_id).pdf +// report_id 本身已经包含时间戳和随机数,所以 MD5 后就是唯一的 +func (m *PDFCacheManager) GetCacheKeyByReportID(reportID string) string { + hash := md5.Sum([]byte(reportID)) + return hex.EncodeToString(hash[:]) +} + // GetCachePath 获取缓存文件路径 func (m *PDFCacheManager) GetCachePath(cacheKey string) string { return filepath.Join(m.cacheDir, fmt.Sprintf("%s.pdf", cacheKey)) @@ -105,6 +113,20 @@ func (m *PDFCacheManager) GetByCacheKey(cacheKey string) ([]byte, bool, time.Tim return m.getByKey(cacheKey, "", "") } +// SetByReportID 将PDF文件保存到缓存(基于报告ID) +func (m *PDFCacheManager) SetByReportID(reportID string, pdfBytes []byte) error { + cacheKey := m.GetCacheKeyByReportID(reportID) + return m.setByKey(cacheKey, pdfBytes, reportID, "") +} + +// GetByReportID 从缓存获取PDF文件(基于报告ID) +// 直接通过 report_id 的 MD5 计算文件名,无需遍历 +// 返回PDF字节流、是否命中缓存、文件创建时间、错误 +func (m *PDFCacheManager) GetByReportID(reportID string) ([]byte, bool, time.Time, error) { + cacheKey := m.GetCacheKeyByReportID(reportID) + return m.getByKey(cacheKey, reportID, "") +} + // getByKey 内部方法:根据缓存键获取文件 func (m *PDFCacheManager) getByKey(cacheKey string, key1, key2 string) ([]byte, bool, time.Time, error) { cachePath := m.GetCachePath(cacheKey) From 29c49d6a0003231a31cd3855ca2d351d83df4ea5 Mon Sep 17 00:00:00 2001 From: liangzai <2440983361@qq.com> Date: Thu, 29 Jan 2026 16:40:19 +0800 Subject: [PATCH 9/9] f --- .../api/services/processors/pdfg/pdfg01gz_processor.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/domains/api/services/processors/pdfg/pdfg01gz_processor.go b/internal/domains/api/services/processors/pdfg/pdfg01gz_processor.go index e0346bc..7601082 100644 --- a/internal/domains/api/services/processors/pdfg/pdfg01gz_processor.go +++ b/internal/domains/api/services/processors/pdfg/pdfg01gz_processor.go @@ -392,17 +392,17 @@ func formatDataForPDF(apiData map[string]interface{}, params dto.PDFG01GZReq, lo }, }) - // 2. IVYZ5A9O - 自然人综合风险智能评估模型 - if data, ok := apiData["IVYZ5A9O"]; ok && data != nil { + // 2. JRZQ0L85 - 自然人综合风险智能评估模型(替代原IVYZ5A9O) + if data, ok := apiData["JRZQ0L85"]; ok && data != nil { result = append(result, map[string]interface{}{ - "apiID": "IVYZ5A9O", + "apiID": "JRZQ0L85", "data": data, }) } else { // 子处理器失败或无数据时,返回空对象 {} - logger.Debug("IVYZ5A9O数据缺失,使用空对象") + logger.Debug("JRZQ0L85数据缺失,使用空对象") result = append(result, map[string]interface{}{ - "apiID": "IVYZ5A9O", + "apiID": "JRZQ0L85", "data": map[string]interface{}{}, }) }