f
This commit is contained in:
@@ -90,6 +90,8 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ./logs:/app/logs
|
- ./logs:/app/logs
|
||||||
- ./resources/Pure_Component:/app/resources/Pure_Component
|
- ./resources/Pure_Component:/app/resources/Pure_Component
|
||||||
|
# 持久化PDF缓存目录,确保生成的PDF在容器重启后仍然存在
|
||||||
|
- ./storage/pdfg-cache:/app/storage/pdfg-cache
|
||||||
# user: "1001:1001" # 注释掉,使用root权限运行
|
# user: "1001:1001" # 注释掉,使用root权限运行
|
||||||
networks:
|
networks:
|
||||||
- tyapi-network
|
- tyapi-network
|
||||||
|
|||||||
@@ -94,13 +94,16 @@ 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)
|
||||||
|
|
||||||
// 检查缓存(基于姓名+身份证)
|
// 检查缓存(基于姓名+身份证)
|
||||||
_, hit, createdAt, err := cacheManager.Get(paramsDto.Name, paramsDto.IDCard)
|
_, hit, createdAt, err := cacheManager.Get(paramsDto.Name, paramsDto.IDCard)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zapLogger.Warn("检查缓存失败,继续生成PDF", zap.Error(err))
|
zapLogger.Warn("检查缓存失败,继续生成PDF", zap.Error(err))
|
||||||
} else if hit {
|
} else if hit {
|
||||||
// 计算缓存键,作为报告ID(可持久化)
|
// 生成对外可见的报告ID(包含随机前缀,防止用户直接推断缓存键)
|
||||||
reportID := cacheManager.GetCacheKey(paramsDto.Name, paramsDto.IDCard)
|
reportID := generateReportID(cacheKey)
|
||||||
|
|
||||||
// 缓存命中,模拟生成耗时(约4秒)
|
// 缓存命中,模拟生成耗时(约4秒)
|
||||||
zapLogger.Info("PDF缓存命中,返回缓存文件",
|
zapLogger.Info("PDF缓存命中,返回缓存文件",
|
||||||
@@ -158,9 +161,6 @@ func ProcessPDFG01GZRequest(ctx context.Context, params []byte, deps *processors
|
|||||||
reportNumber = generateReportNumber()
|
reportNumber = generateReportNumber()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算报告ID(与缓存键一致,便于通过ID直接下载)
|
|
||||||
reportID := cacheManager.GetCacheKey(paramsDto.Name, paramsDto.IDCard)
|
|
||||||
|
|
||||||
// 构建PDF生成请求
|
// 构建PDF生成请求
|
||||||
pdfReq := &pdfgen.GeneratePDFRequest{
|
pdfReq := &pdfgen.GeneratePDFRequest{
|
||||||
Data: formattedData,
|
Data: formattedData,
|
||||||
@@ -189,6 +189,9 @@ func ProcessPDFG01GZRequest(ctx context.Context, params []byte, deps *processors
|
|||||||
// 不影响返回结果,只记录警告
|
// 不影响返回结果,只记录警告
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 生成报告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)
|
||||||
@@ -499,6 +502,13 @@ func generateReportNumber() string {
|
|||||||
return fmt.Sprintf("RPT%s", time.Now().Format("20060102150405"))
|
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/缓存键)
|
// generateDownloadURL 生成下载链接(基于报告ID/缓存键)
|
||||||
// apiDomain: 外部可访问的API域名,如 api.tianyuanapi.com
|
// apiDomain: 外部可访问的API域名,如 api.tianyuanapi.com
|
||||||
func generateDownloadURL(apiDomain, reportID string) string {
|
func generateDownloadURL(apiDomain, reportID string) string {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package handlers
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@@ -35,18 +36,25 @@ 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) {
|
||||||
reportID := c.Query("id")
|
rawReportID := c.Query("id")
|
||||||
|
|
||||||
if reportID == "" {
|
if rawReportID == "" {
|
||||||
h.responseBuilder.BadRequest(c, "报告ID不能为空")
|
h.responseBuilder.BadRequest(c, "报告ID不能为空")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 从对外报告ID中解析内部缓存键(使用最后一个'-'后的部分作为缓存键)
|
||||||
|
cacheKey := rawReportID
|
||||||
|
if idx := strings.LastIndex(rawReportID, "-"); idx > 0 && idx < len(rawReportID)-1 {
|
||||||
|
cacheKey = rawReportID[idx+1:]
|
||||||
|
}
|
||||||
|
|
||||||
// 从缓存获取PDF
|
// 从缓存获取PDF
|
||||||
pdfBytes, hit, createdAt, err := h.cacheManager.GetByCacheKey(reportID)
|
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", reportID),
|
zap.String("report_id", rawReportID),
|
||||||
|
zap.String("cache_key", cacheKey),
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
)
|
)
|
||||||
h.responseBuilder.InternalError(c, "获取PDF文件失败")
|
h.responseBuilder.InternalError(c, "获取PDF文件失败")
|
||||||
@@ -55,7 +63,8 @@ func (h *PDFGHandler) DownloadPDF(c *gin.Context) {
|
|||||||
|
|
||||||
if !hit {
|
if !hit {
|
||||||
h.logger.Warn("PDF文件不存在或已过期",
|
h.logger.Warn("PDF文件不存在或已过期",
|
||||||
zap.String("report_id", reportID),
|
zap.String("report_id", rawReportID),
|
||||||
|
zap.String("cache_key", cacheKey),
|
||||||
)
|
)
|
||||||
h.responseBuilder.NotFound(c, "PDF文件不存在或已过期,请重新生成")
|
h.responseBuilder.NotFound(c, "PDF文件不存在或已过期,请重新生成")
|
||||||
return
|
return
|
||||||
@@ -65,7 +74,8 @@ 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", reportID),
|
zap.String("report_id", rawReportID),
|
||||||
|
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文件已过期,请重新生成")
|
||||||
@@ -74,14 +84,22 @@ func (h *PDFGHandler) DownloadPDF(c *gin.Context) {
|
|||||||
|
|
||||||
// 设置响应头
|
// 设置响应头
|
||||||
c.Header("Content-Type", "application/pdf")
|
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)))
|
c.Header("Content-Length", fmt.Sprintf("%d", len(pdfBytes)))
|
||||||
|
|
||||||
// 发送PDF文件
|
// 发送PDF文件
|
||||||
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", reportID),
|
zap.String("report_id", rawReportID),
|
||||||
|
zap.String("cache_key", cacheKey),
|
||||||
zap.Int("file_size", len(pdfBytes)),
|
zap.Int("file_size", len(pdfBytes)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user