f
This commit is contained in:
@@ -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, ¶msDto); 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("无法获取处理器: %s,CombService不支持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)
|
||||
}
|
||||
Reference in New Issue
Block a user