f
This commit is contained in:
@@ -2,6 +2,8 @@ package pdfg
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -94,9 +96,6 @@ func ProcessPDFG01GZRequest(ctx context.Context, params []byte, deps *processors
|
|||||||
pdfGenService = pdfgen.NewPDFGenService(defaultCfg, zapLogger)
|
pdfGenService = pdfgen.NewPDFGenService(defaultCfg, zapLogger)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算缓存键(内部使用,不直接暴露给用户)
|
|
||||||
cacheKey := cacheManager.GetCacheKey(paramsDto.Name, paramsDto.IDCard)
|
|
||||||
|
|
||||||
// 直接生成PDF,不检查缓存(每次都重新生成)
|
// 直接生成PDF,不检查缓存(每次都重新生成)
|
||||||
zapLogger.Info("开始生成PDF",
|
zapLogger.Info("开始生成PDF",
|
||||||
zap.String("name", paramsDto.Name),
|
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))
|
return nil, errors.Join(processors.ErrSystem, fmt.Errorf("生成PDF失败: %w", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存到缓存(基于姓名+身份证)
|
// 生成报告ID(每次请求都生成唯一的ID)
|
||||||
if err := cacheManager.Set(paramsDto.Name, paramsDto.IDCard, pdfResp.PDFBytes); err != nil {
|
reportID := generateReportID()
|
||||||
|
|
||||||
|
// 保存到缓存(基于报告ID,文件名包含时间戳确保唯一性)
|
||||||
|
if err := cacheManager.SetByReportID(reportID, pdfResp.PDFBytes); err != nil {
|
||||||
zapLogger.Warn("保存PDF到缓存失败", zap.Error(err))
|
zapLogger.Warn("保存PDF到缓存失败", zap.Error(err))
|
||||||
// 不影响返回结果,只记录警告
|
// 不影响返回结果,只记录警告
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成报告ID(与内部缓存键解耦,对外只暴露带前缀的ID)
|
|
||||||
reportID := generateReportID(cacheKey)
|
|
||||||
|
|
||||||
// 生成下载链接(基于报告ID)
|
// 生成下载链接(基于报告ID)
|
||||||
downloadURL := generateDownloadURL(apiDomain, reportID)
|
downloadURL := generateDownloadURL(apiDomain, reportID)
|
||||||
expiresAt := time.Now().Add(cacheTTL)
|
expiresAt := time.Now().Add(cacheTTL)
|
||||||
@@ -473,10 +472,18 @@ func generateReportNumber() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// generateReportID 生成对外可见的报告ID
|
// generateReportID 生成对外可见的报告ID
|
||||||
// 结构:{report_number}-{cacheKey},前缀变化以避免用户轻易看出是否命中缓存
|
// 每次请求都生成唯一的ID,格式:{report_number}-{随机字符串}
|
||||||
func generateReportID(cacheKey string) string {
|
// 注意:不再包含cacheKey,因为每次请求都会重新生成,不需要通过ID定位缓存文件
|
||||||
|
func generateReportID() string {
|
||||||
reportNumber := generateReportNumber()
|
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/缓存键)
|
// generateDownloadURL 生成下载链接(基于报告ID/缓存键)
|
||||||
|
|||||||
@@ -36,25 +36,18 @@ func NewPDFGHandler(
|
|||||||
// DownloadPDF 下载PDF文件
|
// DownloadPDF 下载PDF文件
|
||||||
// GET /api/v1/pdfg/download?id=报告ID
|
// GET /api/v1/pdfg/download?id=报告ID
|
||||||
func (h *PDFGHandler) DownloadPDF(c *gin.Context) {
|
func (h *PDFGHandler) DownloadPDF(c *gin.Context) {
|
||||||
rawReportID := c.Query("id")
|
reportID := c.Query("id")
|
||||||
|
|
||||||
if rawReportID == "" {
|
if reportID == "" {
|
||||||
h.responseBuilder.BadRequest(c, "报告ID不能为空")
|
h.responseBuilder.BadRequest(c, "报告ID不能为空")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从对外报告ID中解析内部缓存键(使用最后一个'-'后的部分作为缓存键)
|
// 通过报告ID获取PDF文件
|
||||||
cacheKey := rawReportID
|
pdfBytes, hit, createdAt, err := h.cacheManager.GetByReportID(reportID)
|
||||||
if idx := strings.LastIndex(rawReportID, "-"); idx > 0 && idx < len(rawReportID)-1 {
|
|
||||||
cacheKey = rawReportID[idx+1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从缓存获取PDF
|
|
||||||
pdfBytes, hit, createdAt, err := h.cacheManager.GetByCacheKey(cacheKey)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logger.Error("获取PDF缓存失败",
|
h.logger.Error("获取PDF缓存失败",
|
||||||
zap.String("report_id", rawReportID),
|
zap.String("report_id", reportID),
|
||||||
zap.String("cache_key", cacheKey),
|
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
)
|
)
|
||||||
h.responseBuilder.InternalError(c, "获取PDF文件失败")
|
h.responseBuilder.InternalError(c, "获取PDF文件失败")
|
||||||
@@ -63,8 +56,7 @@ func (h *PDFGHandler) DownloadPDF(c *gin.Context) {
|
|||||||
|
|
||||||
if !hit {
|
if !hit {
|
||||||
h.logger.Warn("PDF文件不存在或已过期",
|
h.logger.Warn("PDF文件不存在或已过期",
|
||||||
zap.String("report_id", rawReportID),
|
zap.String("report_id", reportID),
|
||||||
zap.String("cache_key", cacheKey),
|
|
||||||
)
|
)
|
||||||
h.responseBuilder.NotFound(c, "PDF文件不存在或已过期,请重新生成")
|
h.responseBuilder.NotFound(c, "PDF文件不存在或已过期,请重新生成")
|
||||||
return
|
return
|
||||||
@@ -74,8 +66,7 @@ func (h *PDFGHandler) DownloadPDF(c *gin.Context) {
|
|||||||
expiresAt := createdAt.Add(24 * time.Hour)
|
expiresAt := createdAt.Add(24 * time.Hour)
|
||||||
if time.Now().After(expiresAt) {
|
if time.Now().After(expiresAt) {
|
||||||
h.logger.Warn("PDF文件已过期",
|
h.logger.Warn("PDF文件已过期",
|
||||||
zap.String("report_id", rawReportID),
|
zap.String("report_id", reportID),
|
||||||
zap.String("cache_key", cacheKey),
|
|
||||||
zap.Time("expires_at", expiresAt),
|
zap.Time("expires_at", expiresAt),
|
||||||
)
|
)
|
||||||
h.responseBuilder.NotFound(c, "PDF文件已过期,请重新生成")
|
h.responseBuilder.NotFound(c, "PDF文件已过期,请重新生成")
|
||||||
@@ -84,11 +75,11 @@ func (h *PDFGHandler) DownloadPDF(c *gin.Context) {
|
|||||||
|
|
||||||
// 设置响应头
|
// 设置响应头
|
||||||
c.Header("Content-Type", "application/pdf")
|
c.Header("Content-Type", "application/pdf")
|
||||||
// 使用报告ID前缀作为下载文件名的一部分,避免泄露内部缓存键
|
// 使用报告ID前缀作为下载文件名的一部分
|
||||||
filename := "大数据租赁风险报告.pdf"
|
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)
|
filename = fmt.Sprintf("大数据租赁风险报告_%s.pdf", prefix)
|
||||||
}
|
}
|
||||||
c.Header("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, filename))
|
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)
|
c.Data(http.StatusOK, "application/pdf", pdfBytes)
|
||||||
|
|
||||||
h.logger.Info("PDF文件下载成功",
|
h.logger.Info("PDF文件下载成功",
|
||||||
zap.String("report_id", rawReportID),
|
zap.String("report_id", reportID),
|
||||||
zap.String("cache_key", cacheKey),
|
|
||||||
zap.Int("file_size", len(pdfBytes)),
|
zap.Int("file_size", len(pdfBytes)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,6 +78,14 @@ func (m *PDFCacheManager) GetCacheKeyByProduct(productID, version string) string
|
|||||||
return hex.EncodeToString(hash[:])
|
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 获取缓存文件路径
|
// GetCachePath 获取缓存文件路径
|
||||||
func (m *PDFCacheManager) GetCachePath(cacheKey string) string {
|
func (m *PDFCacheManager) GetCachePath(cacheKey string) string {
|
||||||
return filepath.Join(m.cacheDir, fmt.Sprintf("%s.pdf", cacheKey))
|
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, "", "")
|
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 内部方法:根据缓存键获取文件
|
// getByKey 内部方法:根据缓存键获取文件
|
||||||
func (m *PDFCacheManager) getByKey(cacheKey string, key1, key2 string) ([]byte, bool, time.Time, error) {
|
func (m *PDFCacheManager) getByKey(cacheKey string, key1, key2 string) ([]byte, bool, time.Time, error) {
|
||||||
cachePath := m.GetCachePath(cacheKey)
|
cachePath := m.GetCachePath(cacheKey)
|
||||||
|
|||||||
Reference in New Issue
Block a user