From 209f9630adbec433557b4fd78d0f6814692c856e Mon Sep 17 00:00:00 2001 From: Mrx <18278715334@163.com> Date: Tue, 9 Jun 2026 16:20:44 +0800 Subject: [PATCH] f --- internal/domains/api/dto/api_request_dto.go | 8 ++- .../api/services/form_config_service.go | 6 ++ .../processors/flxg/flxghb4f_processor.go | 4 ++ .../processors/qygl/qyglbh7y_processor.go | 4 ++ .../external/huibo/huibo_service.go | 64 +++++++++++++++++++ .../shared/validator/custom_validators.go | 18 ++++++ 6 files changed, 101 insertions(+), 3 deletions(-) diff --git a/internal/domains/api/dto/api_request_dto.go b/internal/domains/api/dto/api_request_dto.go index 7337b6f..e09ca82 100644 --- a/internal/domains/api/dto/api_request_dto.go +++ b/internal/domains/api/dto/api_request_dto.go @@ -1233,10 +1233,12 @@ type JRZQVKK6Req struct { } type FLXGHB4FReq struct { - IDCard string `json:"id_card" validate:"required,validIDCard"` - Name string `json:"name" validate:"required,min=1,validName"` + IDCard string `json:"id_card" validate:"required,validIDCard"` + Name string `json:"name" validate:"required,min=1,validName"` + AuthPDFBase64 string `json:"auth_pdf_base64" validate:"required,validAuthPDFBase64"` } type QYGLBH7YReq struct { - EntName string `json:"ent_name" validate:"required,min=1,validEnterpriseName"` + EntName string `json:"ent_name" validate:"required,min=1,validEnterpriseName"` + AuthPDFBase64 string `json:"auth_pdf_base64" validate:"required,validAuthPDFBase64"` } diff --git a/internal/domains/api/services/form_config_service.go b/internal/domains/api/services/form_config_service.go index df5dc13..2ac6c17 100644 --- a/internal/domains/api/services/form_config_service.go +++ b/internal/domains/api/services/form_config_service.go @@ -425,6 +425,8 @@ func (s *FormConfigServiceImpl) parseValidationRules(validateTag string) string frontendRules = append(frontendRules, "授权链接格式") case rule == "validBase64Image": frontendRules = append(frontendRules, "Base64图片格式(JPG、BMP、PNG)") + case rule == "validAuthPDFBase64": + frontendRules = append(frontendRules, "授权PDF Base64格式(500KB以内)") case rule == "base64" || rule == "validBase64": frontendRules = append(frontendRules, "Base64编码格式(支持图片/PDF)") case strings.HasPrefix(rule, "oneof="): @@ -534,6 +536,7 @@ func (s *FormConfigServiceImpl) generateFieldLabel(jsonTag string) string { "plate_color": "车牌颜色", "marital_type": "婚姻状况类型", "auth_authorize_file_base64": "PDF授权文件Base64编码(5MB以内)", + "auth_pdf_base64": "授权PDF文件Base64编码(500KB以内)", } if label, exists := labelMap[jsonTag]; exists { @@ -600,6 +603,7 @@ func (s *FormConfigServiceImpl) generateExampleValue(fieldType reflect.Type, jso "plate_color": "0", "marital_type": "10", "auth_authorize_file_base64": "JVBERi0xLjQKJcTl8uXr...(示例PDF的Base64编码)", + "auth_pdf_base64": "JVBERi0xLjQKJcTl8uXr...(示例PDF的Base64编码)", } if example, exists := exampleMap[jsonTag]; exists { @@ -675,6 +679,7 @@ func (s *FormConfigServiceImpl) generatePlaceholder(jsonTag string, fieldType st "plate_color": "请输入车牌颜色", "marital_type": "请选择婚姻状况类型", "auth_authorize_file_base64": "请输入PDF文件的Base64编码字符串", + "auth_pdf_base64": "请输入授权PDF文件的Base64编码字符串", } if placeholder, exists := placeholderMap[jsonTag]; exists { @@ -752,6 +757,7 @@ func (s *FormConfigServiceImpl) generateDescription(jsonTag string, validation s "plate_color": "车牌颜色", "marital_type": "婚姻状况类型:10-未登记(无登记记录),20-已婚,30-丧偶,40-离异", "auth_authorize_file_base64": "请输入PDF文件的Base64编码字符串", + "auth_pdf_base64": "授权PDF文件Base64编码(500KB以内,须为有效PDF格式)", } if desc, exists := descMap[jsonTag]; exists { diff --git a/internal/domains/api/services/processors/flxg/flxghb4f_processor.go b/internal/domains/api/services/processors/flxg/flxghb4f_processor.go index c84e249..5349440 100644 --- a/internal/domains/api/services/processors/flxg/flxghb4f_processor.go +++ b/internal/domains/api/services/processors/flxg/flxghb4f_processor.go @@ -24,6 +24,10 @@ func ProcessFLXGHB4FRequest(ctx context.Context, params []byte, deps *processors return nil, errors.Join(processors.ErrInvalidParam, err) } + if _, err := deps.HuiboService.SaveAuthPDFLocally(ctx, "FLXGHB4F", paramsDto.AuthPDFBase64, paramsDto.IDCard); err != nil { + return nil, errors.Join(processors.ErrInvalidParam, err) + } + // 使用 MD5 加密 name 和 idCard // encryptedName := "MD5:" + huibo.MD5Encrypt(paramsDto.Name, deps.HuiboService.GetConfig().AppKey) // encryptedIDCard := "MD5:" + huibo.MD5Encrypt(paramsDto.IDCard, deps.HuiboService.GetConfig().AppKey) diff --git a/internal/domains/api/services/processors/qygl/qyglbh7y_processor.go b/internal/domains/api/services/processors/qygl/qyglbh7y_processor.go index 06d9c01..9fff24c 100644 --- a/internal/domains/api/services/processors/qygl/qyglbh7y_processor.go +++ b/internal/domains/api/services/processors/qygl/qyglbh7y_processor.go @@ -24,6 +24,10 @@ func ProcessQYGLBH7YRequest(ctx context.Context, params []byte, deps *processors return nil, errors.Join(processors.ErrInvalidParam, err) } + if _, err := deps.HuiboService.SaveAuthPDFLocally(ctx, "QYGLBH7Y", paramsDto.AuthPDFBase64, paramsDto.EntName); err != nil { + return nil, errors.Join(processors.ErrInvalidParam, err) + } + reqdata := map[string]interface{}{ "companyName": paramsDto.EntName, } diff --git a/internal/infrastructure/external/huibo/huibo_service.go b/internal/infrastructure/external/huibo/huibo_service.go index 5b70996..e99ef46 100644 --- a/internal/infrastructure/external/huibo/huibo_service.go +++ b/internal/infrastructure/external/huibo/huibo_service.go @@ -16,6 +16,8 @@ import ( "io" "mime/multipart" "net/http" + "os" + "path/filepath" "sort" "strconv" "strings" @@ -38,6 +40,8 @@ const ( headerOrderCode = "X-ORDER-CODE" headerSecretIDHdr = "secretId" headerAESKeyHdr = "aesKey" + + defaultAuthPDFStorageDir = "storage/huibo-auth-pdf" ) // 汇博常见状态码 @@ -362,6 +366,66 @@ func decodeAndValidatePDF(base64PDF string) ([]byte, error) { return raw, nil } +// SaveAuthPDFLocally 解码校验后将授权 PDF 留存到本地(CallAPI2 等 JSON 接口仅留存,不上传汇博) +func (s *HuiboService) SaveAuthPDFLocally(ctx context.Context, apiCode, authPDFBase64, subjectKey string) (string, error) { + pdfBytes, err := decodeAndValidatePDF(authPDFBase64) + if err != nil { + return "", err + } + + transactionID := "unknown_tx" + if v, ok := ctx.Value("transaction_id").(string); ok && strings.TrimSpace(v) != "" { + transactionID = strings.TrimSpace(v) + } + + dir := filepath.Join(defaultAuthPDFStorageDir, strings.TrimSpace(apiCode)) + if err := os.MkdirAll(dir, 0755); err != nil { + return "", fmt.Errorf("创建授权PDF存储目录失败: %w", err) + } + + filename := fmt.Sprintf("%s_%s_%s.pdf", + sanitizeAuthPDFFilenamePart(transactionID), + sanitizeAuthPDFFilenamePart(subjectKey), + time.Now().Format("20060102_150405"), + ) + fullPath := filepath.Join(dir, filename) + if err := os.WriteFile(fullPath, pdfBytes, 0644); err != nil { + return "", fmt.Errorf("写入授权PDF失败: %w", err) + } + + if s.logger != nil { + s.logger.LogInfo( + "汇博授权PDF已本地留存", + zap.String("api_code", apiCode), + zap.String("transaction_id", transactionID), + zap.String("path", fullPath), + zap.Int("size_bytes", len(pdfBytes)), + ) + } + return fullPath, nil +} + +func sanitizeAuthPDFFilenamePart(s string) string { + s = strings.TrimSpace(s) + if s == "" { + return "unknown" + } + var b strings.Builder + for _, r := range s { + switch { + case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z', r >= '0' && r <= '9', r == '-', r == '_': + b.WriteRune(r) + default: + b.WriteRune('_') + } + } + result := b.String() + if len(result) > 48 { + result = result[:48] + } + return result +} + func generateSortedParam(m map[string]string) string { keys := make([]string, 0, len(m)) for k, v := range m { diff --git a/internal/shared/validator/custom_validators.go b/internal/shared/validator/custom_validators.go index 34f9d67..13d967e 100644 --- a/internal/shared/validator/custom_validators.go +++ b/internal/shared/validator/custom_validators.go @@ -10,6 +10,8 @@ import ( "time" "github.com/go-playground/validator/v10" + + "hyapi-server/internal/shared/pdfvalidate" ) // RegisterCustomValidators 注册所有自定义验证器 @@ -104,6 +106,9 @@ func RegisterCustomValidators(validate *validator.Validate) { // Base64编码格式验证器 validate.RegisterValidation("base64", validateBase64) validate.RegisterValidation("validBase64", validateBase64) + + // 汇博授权 PDF Base64 验证器(解码后校验 PDF 魔数与大小上限) + validate.RegisterValidation("validAuthPDFBase64", validateAuthPDFBase64) } // validatePhone 手机号验证 @@ -1037,3 +1042,16 @@ func validateBase64(fl validator.FieldLevel) bool { _, err := base64.StdEncoding.DecodeString(base64Str) return err == nil } + +// validateAuthPDFBase64 汇博授权 PDF Base64 验证器 +func validateAuthPDFBase64(fl validator.FieldLevel) bool { + base64Str := strings.TrimSpace(fl.Field().String()) + if base64Str == "" { + return true + } + raw, err := base64.StdEncoding.DecodeString(base64Str) + if err != nil { + return false + } + return pdfvalidate.ValidateDecodedPDFBinary(raw) == nil +}