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] 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)