This commit is contained in:
2026-04-21 22:36:48 +08:00
commit 488c695fdf
748 changed files with 266838 additions and 0 deletions

View File

@@ -0,0 +1,498 @@
package pdfg
import (
"context"
"crypto/rand"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"time"
"hyapi-server/internal/config"
"hyapi-server/internal/domains/api/dto"
"hyapi-server/internal/domains/api/services/processors"
"hyapi-server/internal/infrastructure/external/pdfgen"
"hyapi-server/internal/shared/logger"
"hyapi-server/internal/shared/pdf"
"go.uber.org/zap"
)
// ProcessPDFG01GZRequest PDFG01GZ 处理器 - 大数据租赁风险PDF报告
func ProcessPDFG01GZRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
var paramsDto dto.PDFG01GZReq
if err := json.Unmarshal(params, &paramsDto); err != nil {
return nil, errors.Join(processors.ErrSystem, err)
}
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
return nil, errors.Join(processors.ErrInvalidParam, err)
}
// 获取全局logger
zapLogger := logger.GetGlobalLogger()
// Debug记录入口参数使用 Info 级别便于线上查看)
logger.L().Info("PDFG01GZ请求开始",
zap.String("name", paramsDto.Name),
zap.String("id_card", paramsDto.IDCard),
zap.String("mobile_no", paramsDto.MobileNo),
zap.String("authorized", paramsDto.Authorized),
)
// 从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
}
// 获取最大缓存大小
var maxSize int64
if cfg, ok := ctx.Value("config").(*config.Config); ok && cfg != nil {
maxSize = cfg.PDFGen.Cache.MaxSize
// 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),
zap.String("pdfgen_api_path", cfg.PDFGen.APIPath),
zap.Duration("pdfgen_timeout", cfg.PDFGen.Timeout),
zap.String("cache_dir", cacheDir),
zap.Duration("cache_ttl", cacheTTL),
zap.Int64("cache_max_size", maxSize),
)
}
// 创建PDF缓存管理器
cacheManager, err := pdf.NewPDFCacheManager(zapLogger, cacheDir, cacheTTL, maxSize)
if err != nil {
return nil, errors.Join(processors.ErrSystem, fmt.Errorf("创建PDF缓存管理器失败: %w", err))
}
// 从context获取config创建PDF生成服务
var pdfGenService *pdfgen.PDFGenService
if cfg, ok := ctx.Value("config").(*config.Config); ok && cfg != nil {
pdfGenService = pdfgen.NewPDFGenService(cfg, zapLogger)
} else {
// 如果无法获取config使用默认配置
defaultCfg := &config.Config{
App: config.AppConfig{Env: "development"},
PDFGen: config.PDFGenConfig{
DevelopmentURL: "http://pdfg.haiyudata.com",
ProductionURL: "http://localhost:15990",
APIPath: "/api/v1/generate/guangzhou",
Timeout: 120 * time.Second,
},
}
pdfGenService = pdfgen.NewPDFGenService(defaultCfg, zapLogger)
}
// 直接生成PDF不检查缓存每次都重新生成
zapLogger.Info("开始生成PDF",
zap.String("name", paramsDto.Name),
zap.String("id_card", paramsDto.IDCard),
)
// 调用多个处理器获取数据(即使部分失败也继续)
apiData := collectAPIData(ctx, paramsDto, deps, zapLogger)
// 格式化数据为PDF生成服务需要的格式为缺失的数据提供默认值
formattedData := formatDataForPDF(apiData, paramsDto, zapLogger)
// 打印完整的apiData为避免日志过大这里直接序列化为JSON字符串
apiDataBytes, _ := json.Marshal(apiData)
logger.L().Info("PDFG01GZ数据准备完成",
zap.Int("api_data_count", len(apiData)),
zap.Int("formatted_items", len(formattedData)),
zap.ByteString("api_data", apiDataBytes),
)
// 从APPLICANT_BASIC_INFO中提取报告编号如果存在
var reportNumber string
if len(formattedData) > 0 {
if basicInfo, ok := formattedData[0]["data"].(map[string]interface{}); ok {
if rn, ok := basicInfo["report_number"].(string); ok {
reportNumber = rn
}
}
}
// 如果没有提取到,生成新的报告编号
if reportNumber == "" {
reportNumber = generateReportNumber()
}
// 构建PDF生成请求
pdfReq := &pdfgen.GeneratePDFRequest{
Data: formattedData,
ReportNumber: reportNumber,
GenerateTime: time.Now().Format("2006-01-02 15:04:05"),
}
// 调用PDF生成服务
// 即使部分子处理器失败只要有APPLICANT_BASIC_INFO就可以生成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失败",
zap.Error(err),
zap.Int("data_items", len(formattedData)),
)
return nil, errors.Join(processors.ErrSystem, fmt.Errorf("生成PDF失败: %w", err))
}
// 生成报告ID每次请求都生成唯一的ID
reportID := generateReportID()
// 保存到缓存基于报告ID文件名包含时间戳确保唯一性
if err := cacheManager.SetByReportID(reportID, pdfResp.PDFBytes); err != nil {
zapLogger.Warn("保存PDF到缓存失败", zap.Error(err))
// 不影响返回结果,只记录警告
}
// 生成下载链接基于报告ID
downloadURL := generateDownloadURL(apiDomain, reportID)
expiresAt := time.Now().Add(cacheTTL)
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,
"expires_at": expiresAt.Format(time.RFC3339),
"ttl_seconds": int(cacheTTL.Seconds()),
})
}
// collectAPIData 收集所有需要的API数据
// 即使部分或全部子处理器失败也会返回结果失败的设为nil确保流程继续
func collectAPIData(ctx context.Context, params dto.PDFG01GZReq, deps *processors.ProcessorDependencies, logger *zap.Logger) map[string]interface{} {
apiData := make(map[string]interface{})
// 并发调用多个处理器
type processorResult struct {
apiCode string
data interface{}
err error
}
results := make(chan processorResult, 5)
// 调用JRZQ0L85 - 需要: name, id_card, mobile_no
go func() {
defer func() {
if r := recover(); r != nil {
logger.Error("调用JRZQ0L85处理器时发生panic",
zap.Any("panic", r),
)
results <- processorResult{"JRZQ0L85", nil, fmt.Errorf("处理器panic: %v", r)}
}
}()
jrzq0l85Params := map[string]interface{}{
"name": params.Name,
"id_card": params.IDCard,
"mobile_no": params.MobileNo,
}
paramsBytes, err := json.Marshal(jrzq0l85Params)
if err != nil {
logger.Warn("序列化JRZQ0L85参数失败", zap.Error(err))
results <- processorResult{"JRZQ0L85", nil, err}
return
}
data, err := callProcessor(ctx, "JRZQ0L85", paramsBytes, deps)
results <- processorResult{"JRZQ0L85", data, err}
}()
// 调用JRZQ8A2D - 需要: name, id_card, mobile_no, authorized
go func() {
defer func() {
if r := recover(); r != nil {
logger.Error("调用JRZQ8A2D处理器时发生panic",
zap.Any("panic", r),
)
results <- processorResult{"JRZQ8A2D", nil, fmt.Errorf("处理器panic: %v", r)}
}
}()
jrzq8a2dParams := map[string]interface{}{
"name": params.Name,
"id_card": params.IDCard,
"mobile_no": params.MobileNo,
"authorized": params.Authorized,
}
paramsBytes, err := json.Marshal(jrzq8a2dParams)
if err != nil {
logger.Warn("序列化JRZQ8A2D参数失败", zap.Error(err))
results <- processorResult{"JRZQ8A2D", nil, err}
return
}
data, err := callProcessor(ctx, "JRZQ8A2D", paramsBytes, deps)
results <- processorResult{"JRZQ8A2D", data, err}
}()
// 调用FLXGDEA9 - 需要: name, id_card, authorized
go func() {
defer func() {
if r := recover(); r != nil {
logger.Error("调用FLXGDEA9处理器时发生panic",
zap.Any("panic", r),
)
results <- processorResult{"FLXGDEA9", nil, fmt.Errorf("处理器panic: %v", r)}
}
}()
flxgParams := map[string]interface{}{
"name": params.Name,
"id_card": params.IDCard,
"authorized": params.Authorized,
}
paramsBytes, err := json.Marshal(flxgParams)
if err != nil {
logger.Warn("序列化FLXGDEA9参数失败", zap.Error(err))
results <- processorResult{"FLXGDEA9", nil, err}
return
}
data, err := callProcessor(ctx, "FLXGDEA9", paramsBytes, deps)
results <- processorResult{"FLXGDEA9", data, err}
}()
// 调用JRZQ1D09 - 需要: name, id_card, mobile_no, authorized
go func() {
defer func() {
if r := recover(); r != nil {
logger.Error("调用JRZQ1D09处理器时发生panic",
zap.Any("panic", r),
)
results <- processorResult{"JRZQ1D09", nil, fmt.Errorf("处理器panic: %v", r)}
}
}()
jrzq1d09Params := map[string]interface{}{
"name": params.Name,
"id_card": params.IDCard,
"mobile_no": params.MobileNo,
"authorized": params.Authorized,
}
paramsBytes, err := json.Marshal(jrzq1d09Params)
if err != nil {
logger.Warn("序列化JRZQ1D09参数失败", zap.Error(err))
results <- processorResult{"JRZQ1D09", nil, err}
return
}
data, err := callProcessor(ctx, "JRZQ1D09", paramsBytes, deps)
results <- processorResult{"JRZQ1D09", data, err}
}()
// 调用JRZQ8B3C - 需要: name, id_card, mobile_no (不需要authorized)
go func() {
defer func() {
if r := recover(); r != nil {
logger.Error("调用JRZQ8B3C处理器时发生panic",
zap.Any("panic", r),
)
results <- processorResult{"JRZQ8B3C", nil, fmt.Errorf("处理器panic: %v", r)}
}
}()
jrzq8b3cParams := map[string]interface{}{
"name": params.Name,
"id_card": params.IDCard,
"mobile_no": params.MobileNo,
}
paramsBytes, err := json.Marshal(jrzq8b3cParams)
if err != nil {
logger.Warn("序列化JRZQ8B3C参数失败", zap.Error(err))
results <- processorResult{"JRZQ8B3C", nil, err}
return
}
data, err := callProcessor(ctx, "JRZQ8B3C", paramsBytes, deps)
results <- processorResult{"JRZQ8B3C", data, err}
}()
// 收集结果,即使所有处理器都失败也继续
successCount := 0
for i := 0; i < 5; i++ {
result := <-results
if result.err != nil {
// 记录错误但不中断流程,允许部分数据缺失
logger.Warn("调用处理器失败,将使用默认值",
zap.String("api_code", result.apiCode),
zap.Error(result.err),
)
apiData[result.apiCode] = nil
} else {
apiData[result.apiCode] = result.data
successCount++
}
}
logger.Info("子处理器调用完成",
zap.Int("total", 5),
zap.Int("success", successCount),
zap.Int("failed", 5-successCount),
)
return apiData
}
// callProcessor 调用指定的处理器
func callProcessor(ctx context.Context, apiCode string, params []byte, deps *processors.ProcessorDependencies) (interface{}, error) {
// 通过CombService获取处理器
if combSvc, ok := deps.CombService.(interface {
GetProcessor(apiCode string) (processors.ProcessorFunc, bool)
}); ok {
processor, exists := combSvc.GetProcessor(apiCode)
if !exists {
return nil, fmt.Errorf("未找到处理器: %s", apiCode)
}
respBytes, err := processor(ctx, params, deps)
if err != nil {
return nil, err
}
var data interface{}
if err := json.Unmarshal(respBytes, &data); err != nil {
return nil, fmt.Errorf("解析响应失败: %w", err)
}
return data, nil
}
// 如果无法通过CombService获取返回错误
return nil, fmt.Errorf("无法获取处理器: %sCombService不支持GetProcessor方法", apiCode)
}
// formatDataForPDF 格式化数据为PDF生成服务需要的格式
// 为所有子处理器提供数据即使失败也提供默认值确保PDF生成服务能收到完整结构
func formatDataForPDF(apiData map[string]interface{}, params dto.PDFG01GZReq, logger *zap.Logger) []map[string]interface{} {
result := make([]map[string]interface{}, 0)
// 1. APPLICANT_BASIC_INFO - 申请人基本信息(始终存在)
result = append(result, map[string]interface{}{
"apiID": "APPLICANT_BASIC_INFO",
"data": map[string]interface{}{
"name": params.Name,
"id_card": params.IDCard,
"mobile": params.MobileNo,
"query_time": time.Now().Format("2006-01-02 15:04:05"),
"report_number": generateReportNumber(),
"generate_time": time.Now().Format("2006-01-02 15:04:05"),
},
})
// 2. JRZQ0L85 - 自然人综合风险智能评估模型替代原IVYZ5A9O
if data, ok := apiData["JRZQ0L85"]; ok && data != nil {
result = append(result, map[string]interface{}{
"apiID": "JRZQ0L85",
"data": data,
})
} else {
// 子处理器失败或无数据时,返回空对象 {}
logger.Debug("JRZQ0L85数据缺失使用空对象")
result = append(result, map[string]interface{}{
"apiID": "JRZQ0L85",
"data": map[string]interface{}{},
})
}
// 3. JRZQ8A2D - 特殊名单验证B
if data, ok := apiData["JRZQ8A2D"]; ok && data != nil {
result = append(result, map[string]interface{}{
"apiID": "JRZQ8A2D",
"data": data,
})
} else {
logger.Debug("JRZQ8A2D数据缺失使用空对象")
result = append(result, map[string]interface{}{
"apiID": "JRZQ8A2D",
"data": map[string]interface{}{},
})
}
// 4. FLXGDEA9 - 公安不良人员名单
if data, ok := apiData["FLXGDEA9"]; ok && data != nil {
result = append(result, map[string]interface{}{
"apiID": "FLXGDEA9",
"data": data,
})
} else {
logger.Debug("FLXGDEA9数据缺失使用空对象")
result = append(result, map[string]interface{}{
"apiID": "FLXGDEA9",
"data": map[string]interface{}{},
})
}
// 5. JRZQ1D09 - 3C租赁申请意向
if data, ok := apiData["JRZQ1D09"]; ok && data != nil {
result = append(result, map[string]interface{}{
"apiID": "JRZQ1D09",
"data": data,
})
} else {
logger.Debug("JRZQ1D09数据缺失使用空对象")
result = append(result, map[string]interface{}{
"apiID": "JRZQ1D09",
"data": map[string]interface{}{},
})
}
// 6. JRZQ8B3C - 个人消费能力等级
if data, ok := apiData["JRZQ8B3C"]; ok && data != nil {
result = append(result, map[string]interface{}{
"apiID": "JRZQ8B3C",
"data": data,
})
} else {
logger.Debug("JRZQ8B3C数据缺失使用空对象")
result = append(result, map[string]interface{}{
"apiID": "JRZQ8B3C",
"data": map[string]interface{}{},
})
}
return result
}
// generateReportNumber 生成报告编号
func generateReportNumber() string {
return fmt.Sprintf("RPT%s", time.Now().Format("20060102150405"))
}
// generateReportID 生成对外可见的报告ID
// 每次请求都生成唯一的ID格式{report_number}-{随机字符串}
// 注意不再包含cacheKey因为每次请求都会重新生成不需要通过ID定位缓存文件
func generateReportID() string {
reportNumber := generateReportNumber()
// 生成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/缓存键)
// apiDomain: 外部可访问的API域名如 api.haiyudata.com
func generateDownloadURL(apiDomain, reportID string) string {
if apiDomain == "" {
// 兜底:保留相对路径,方便本地/测试环境使用
return fmt.Sprintf("/api/v1/pdfg/download?id=%s", reportID)
}
// 生成完整链接例如https://api.haiyudata.com/api/v1/pdfg/download?id=xxx
return fmt.Sprintf("https://%s/api/v1/pdfg/download?id=%s", apiDomain, reportID)
}