This commit is contained in:
2026-01-27 16:26:48 +08:00
parent 3ef7b7d1fb
commit f8806eb71c
19 changed files with 1260 additions and 358 deletions

View File

@@ -0,0 +1,11 @@
package dto
// PDFG01GZReq PDFG01GZ 请求参数
type PDFG01GZReq struct {
Name string `json:"name" validate:"required,min=1,validName"`
IDCard string `json:"id_card" validate:"required,validIDCard"`
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
AuthAuthorizeFileCode string `json:"auth_authorize_file_code" validate:"required"` // IVYZ5A9O需要
Authorized string `json:"authorized" validate:"required,oneof=0 1"` // 授权标识0或1
}

View File

@@ -6,12 +6,14 @@ import (
"fmt"
"tyapi-server/internal/application/api/commands"
"tyapi-server/internal/config"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/domains/api/services/processors/comb"
"tyapi-server/internal/domains/api/services/processors/dwbg"
"tyapi-server/internal/domains/api/services/processors/flxg"
"tyapi-server/internal/domains/api/services/processors/ivyz"
"tyapi-server/internal/domains/api/services/processors/jrzq"
"tyapi-server/internal/domains/api/services/processors/pdfg"
"tyapi-server/internal/domains/api/services/processors/qcxg"
"tyapi-server/internal/domains/api/services/processors/qygl"
"tyapi-server/internal/domains/api/services/processors/test"
@@ -46,6 +48,7 @@ type ApiRequestService struct {
validator interfaces.RequestValidator
processorDeps *processors.ProcessorDependencies
combService *comb.CombService
config *config.Config
}
func NewApiRequestService(
@@ -60,6 +63,7 @@ func NewApiRequestService(
shumaiService *shumai.ShumaiService,
validator interfaces.RequestValidator,
productManagementService *services.ProductManagementService,
cfg *config.Config,
) *ApiRequestService {
// 创建组合包服务
combService := comb.NewCombService(productManagementService)
@@ -79,6 +83,7 @@ func NewApiRequestService(
validator: validator,
processorDeps: processorDeps,
combService: combService,
config: cfg,
}
}
@@ -274,6 +279,9 @@ func registerAllProcessors(combService *comb.CombService) {
"TEST001": test.ProcessTestRequest,
"TEST002": test.ProcessTestErrorRequest,
"TEST003": test.ProcessTestTimeoutRequest,
// PDFG系列处理器 - PDF生成
"PDFG01GZ": pdfg.ProcessPDFG01GZRequest,
}
// 批量注册到组合包服务
@@ -295,6 +303,8 @@ func (a *ApiRequestService) PreprocessRequestApi(ctx context.Context, apiCode st
// 将apiCode放入context供外部服务使用
ctx = context.WithValue(ctx, "api_code", apiCode)
// 将config放入context供处理器使用
ctx = context.WithValue(ctx, "config", a.config)
// 1. 优先查找已注册的自定义处理器
if processor, exists := RequestProcessors[apiCode]; exists {

View File

@@ -242,6 +242,7 @@ func (s *FormConfigServiceImpl) getDTOStruct(ctx context.Context, apiCode string
"IVYZN2P8": &dto.IVYZ9K7FReq{}, //身份证实名认证政务版
"YYSYH6F3": &dto.YYSYH6F3Req{}, //运营商三要素简版即时版查询
"IVYZX5Q2": &dto.IVYZX5Q2Req{}, //活体识别步骤二
"PDFG01GZ": &dto.PDFG01GZReq{}, //
}
// 优先返回已配置的DTO

View File

@@ -32,6 +32,12 @@ func (cs *CombService) RegisterProcessor(apiCode string, processor processors.Pr
cs.processorRegistry[apiCode] = processor
}
// GetProcessor 获取处理器(用于内部调用)
func (cs *CombService) GetProcessor(apiCode string) (processors.ProcessorFunc, bool) {
processor, exists := cs.processorRegistry[apiCode]
return processor, exists
}
// ProcessCombRequest 处理组合包请求 - 实现 CombServiceInterface
func (cs *CombService) ProcessCombRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies, packageCode string) (*processors.CombinedResult, error) {
// 1. 根据组合包code获取产品信息

View File

@@ -0,0 +1,466 @@
package pdfg
import (
"context"
"encoding/json"
"errors"
"fmt"
"time"
"tyapi-server/internal/config"
"tyapi-server/internal/domains/api/dto"
"tyapi-server/internal/domains/api/services/processors"
"tyapi-server/internal/infrastructure/external/pdfgen"
"tyapi-server/internal/shared/logger"
"tyapi-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()
// 从context获取config如果存在
var cacheTTL time.Duration = 24 * time.Hour
var cacheDir 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
}
// 获取最大缓存大小
var maxSize int64
if cfg, ok := ctx.Value("config").(*config.Config); ok && cfg != nil {
maxSize = cfg.PDFGen.Cache.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.tianyuanapi.com",
ProductionURL: "http://localhost:15990",
APIPath: "/api/v1/generate/guangzhou",
Timeout: 120 * time.Second,
},
}
pdfGenService = pdfgen.NewPDFGenService(defaultCfg, zapLogger)
}
// 检查缓存
_, hit, createdAt, err := cacheManager.Get(paramsDto.Name, paramsDto.IDCard)
if err != nil {
zapLogger.Warn("检查缓存失败继续生成PDF", zap.Error(err))
} else if hit {
// 缓存命中,模拟慢几秒
zapLogger.Info("PDF缓存命中返回缓存文件",
zap.String("name", paramsDto.Name),
zap.String("id_card", paramsDto.IDCard),
zap.Time("created_at", createdAt),
)
// 模拟慢几秒2-4秒
time.Sleep(2 * time.Second)
// 生成下载链接
downloadURL := generateDownloadURL(paramsDto.Name, paramsDto.IDCard)
return json.Marshal(map[string]interface{}{
"download_url": downloadURL,
"cached": true,
"created_at": createdAt.Format(time.RFC3339),
})
}
// 缓存未命中需要生成PDF
zapLogger.Info("PDF缓存未命中开始生成PDF",
zap.String("name", paramsDto.Name),
zap.String("id_card", paramsDto.IDCard),
)
// 调用多个处理器获取数据(即使部分失败也继续)
apiData := collectAPIData(ctx, paramsDto, deps, zapLogger)
// 格式化数据为PDF生成服务需要的格式为缺失的数据提供默认值
formattedData := formatDataForPDF(apiData, paramsDto, zapLogger)
// 从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
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))
}
// 保存到缓存
if err := cacheManager.Set(paramsDto.Name, paramsDto.IDCard, pdfResp.PDFBytes); err != nil {
zapLogger.Warn("保存PDF到缓存失败", zap.Error(err))
// 不影响返回结果,只记录警告
}
// 生成下载链接
downloadURL := generateDownloadURL(paramsDto.Name, paramsDto.IDCard)
zapLogger.Info("PDF生成成功",
zap.String("name", paramsDto.Name),
zap.String("id_card", paramsDto.IDCard),
zap.String("report_number", reportNumber),
zap.String("download_url", downloadURL),
)
return json.Marshal(map[string]interface{}{
"download_url": downloadURL,
"report_number": reportNumber,
"cached": false,
})
}
// 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)
// 调用IVYZ5A9O - 需要: name, id_card, auth_authorize_file_code
go func() {
defer func() {
if r := recover(); r != nil {
logger.Error("调用IVYZ5A9O处理器时发生panic",
zap.Any("panic", r),
)
results <- processorResult{"IVYZ5A9O", nil, fmt.Errorf("处理器panic: %v", r)}
}
}()
// 检查必需字段
if params.AuthAuthorizeFileCode == "" {
logger.Warn("IVYZ5A9O缺少auth_authorize_file_code字段跳过调用")
results <- processorResult{"IVYZ5A9O", nil, fmt.Errorf("缺少必需字段: auth_authorize_file_code")}
return
}
ivyzParams := map[string]interface{}{
"name": params.Name,
"id_card": params.IDCard,
"auth_authorize_file_code": params.AuthAuthorizeFileCode,
}
paramsBytes, err := json.Marshal(ivyzParams)
if err != nil {
logger.Warn("序列化IVYZ5A9O参数失败", zap.Error(err))
results <- processorResult{"IVYZ5A9O", nil, err}
return
}
data, err := callProcessor(ctx, "IVYZ5A9O", paramsBytes, deps)
results <- processorResult{"IVYZ5A9O", 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. IVYZ5A9O - 自然人综合风险智能评估模型
if data, ok := apiData["IVYZ5A9O"]; ok && data != nil {
result = append(result, map[string]interface{}{
"apiID": "IVYZ5A9O",
"data": data,
})
} else {
// 子处理器失败或无数据时,返回空对象 {}
logger.Debug("IVYZ5A9O数据缺失使用空对象")
result = append(result, map[string]interface{}{
"apiID": "IVYZ5A9O",
"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"))
}
// generateDownloadURL 生成下载链接
func generateDownloadURL(name, idCard string) string {
// 这里应该生成实际的下载URL
// 暂时返回一个占位符,实际应该根据服务器配置生成
return fmt.Sprintf("/api/v1/pdfg/download?name=%s&id_card=%s", name, idCard)
}