From e9fe7ac30335e037dacbde25fad0beb78f6e2ff1 Mon Sep 17 00:00:00 2001 From: Mrx <18278715334@163.com> Date: Thu, 2 Apr 2026 12:46:42 +0800 Subject: [PATCH] f --- .../certification_application_service_impl.go | 161 +++++++++++------- .../notification/wechat_work_service.go | 52 +++++- .../shared/middleware/daily_rate_limit.go | 6 + 3 files changed, 154 insertions(+), 65 deletions(-) diff --git a/internal/application/certification/certification_application_service_impl.go b/internal/application/certification/certification_application_service_impl.go index ccf3bff..164889d 100644 --- a/internal/application/certification/certification_application_service_impl.go +++ b/internal/application/certification/certification_application_service_impl.go @@ -127,6 +127,35 @@ func (s *CertificationApplicationServiceImpl) SubmitEnterpriseInfo( } } + // 0.5 已通过人工审核或已进入后续流程:幂等返回当前认证数据(不调 e签宝、不新建提交记录) + existsCertEarly, err := s.aggregateService.ExistsByUserID(ctx, cmd.UserID) + if err != nil { + return nil, fmt.Errorf("检查认证记录失败: %w", err) + } + if existsCertEarly { + certEarly, loadErr := s.aggregateService.LoadCertificationByUserID(ctx, cmd.UserID) + if loadErr != nil { + return nil, fmt.Errorf("加载认证信息失败: %w", loadErr) + } + switch certEarly.Status { + case enums.StatusInfoSubmitted, enums.StatusEnterpriseVerified, enums.StatusContractApplied, + enums.StatusContractSigned, enums.StatusCompleted, enums.StatusContractRejected, enums.StatusContractExpired: + meta, metaErr := s.AddStatusMetadata(ctx, certEarly) + if metaErr != nil { + return nil, metaErr + } + resp := s.convertToResponse(certEarly) + if meta != nil { + resp.Metadata = meta + } else { + resp.Metadata = map[string]interface{}{} + } + resp.Metadata["next_action"] = enums.GetUserActionHint(certEarly.Status) + s.logger.Info("企业信息提交幂等返回", zap.String("user_id", cmd.UserID), zap.String("status", string(certEarly.Status))) + return resp, nil + } + } + // 1.5 插入企业信息提交记录(包含扩展字段) record := entities.NewEnterpriseInfoSubmitRecord( cmd.UserID, @@ -301,50 +330,12 @@ func (s *CertificationApplicationServiceImpl) SubmitEnterpriseInfo( return fmt.Errorf("加载认证信息失败: %s", err.Error()) } - // 4. 提交企业信息:暂时跳过人工审核,直接进入「已提交」状态(第三步企业认证) - // 恢复人工审核时改为 cert.SubmitEnterpriseInfoForReview(enterpriseInfo),并将 next_action 改为「请等待管理员审核企业信息」 - - authReq := &esign.EnterpriseAuthRequest{ - CompanyName: enterpriseInfo.CompanyName, - UnifiedSocialCode: enterpriseInfo.UnifiedSocialCode, - LegalPersonName: enterpriseInfo.LegalPersonName, - LegalPersonID: enterpriseInfo.LegalPersonID, - TransactorName: enterpriseInfo.LegalPersonName, - TransactorMobile: enterpriseInfo.LegalPersonPhone, - TransactorID: enterpriseInfo.LegalPersonID, + // 4. 提交企业信息:进入人工审核(三真/企业信息审核);e签宝链接仅在管理员审核通过后生成(见 AdminApproveSubmitRecord) + if err := cert.SubmitEnterpriseInfoForReview(enterpriseInfo); err != nil { + return fmt.Errorf("提交企业信息失败: %s", err.Error()) } - authURL, alreadyVerified, err := s.generateEnterpriseAuthOrDetectVerified(txCtx, authReq) - if err != nil { - s.logger.Error("步骤5.2-生成企业认证链接失败", - zap.String("user_id", cmd.UserID), - zap.Error(err)) - return fmt.Errorf("生成企业认证链接失败: %w", err) - } - if alreadyVerified { - s.logger.Info("步骤5.2-检测到企业已实名,进入自动完成认证流程", - zap.String("user_id", cmd.UserID), - zap.String("company_name", enterpriseInfo.CompanyName)) - // 三方侧已实名:先进入 info_submitted,再在同事务内自动推进到 enterprise_verified。 - if err = cert.SubmitEnterpriseInfo(enterpriseInfo, "", ""); err != nil { - return fmt.Errorf("提交企业信息失败: %s", err.Error()) - } - if err = s.completeEnterpriseVerification(txCtx, cert, cert.UserID, enterpriseInfo.CompanyName, enterpriseInfo.LegalPersonName); err != nil { - return fmt.Errorf("自动完成企业认证失败: %w", err) - } - s.logger.Info("步骤5.2-自动完成企业认证成功", zap.String("user_id", cmd.UserID)) - } else { - s.logger.Info("步骤5.2-企业未实名,写入认证链接并等待用户操作", - zap.String("user_id", cmd.UserID), - zap.String("auth_flow_id", authURL.AuthFlowID)) - err = cert.SubmitEnterpriseInfo(enterpriseInfo, authURL.AuthShortURL, authURL.AuthFlowID) - if err != nil { - return fmt.Errorf("提交企业信息失败: %s", err.Error()) - } - err = s.aggregateService.SaveCertification(txCtx, cert) - if err != nil { - return fmt.Errorf("保存认证信息失败: %s", err.Error()) - } - s.logger.Info("步骤5.2-认证信息保存成功(待用户完成企业认证)", zap.String("user_id", cmd.UserID)) + if err := s.aggregateService.SaveCertification(txCtx, cert); err != nil { + return fmt.Errorf("保存认证信息失败: %s", err.Error()) } // 5. 提交记录与认证状态在同一事务内保存 @@ -355,20 +346,24 @@ func (s *CertificationApplicationServiceImpl) SubmitEnterpriseInfo( zap.String("user_id", cmd.UserID), zap.String("record_id", record.ID)) + var enterpriseInfoMeta map[string]interface{} + if raw, mErr := json.Marshal(enterpriseInfo); mErr == nil { + _ = json.Unmarshal(raw, &enterpriseInfoMeta) + } + if enterpriseInfoMeta == nil { + enterpriseInfoMeta = map[string]interface{}{} + } + enterpriseInfoMeta["submit_at"] = record.SubmitAt.Format(time.RFC3339) + respMeta := map[string]interface{}{ - "enterprise_info": enterpriseInfo, + "enterprise_info": enterpriseInfoMeta, "polling": map[string]interface{}{ - "enabled": !alreadyVerified, + "enabled": false, "endpoint": "/api/v1/certifications/confirm-auth", "interval_seconds": 3, }, - } - if alreadyVerified { - respMeta["next_action"] = "企业已实名,已自动完成认证,可进入合同签署" - respMeta["target_view"] = "contract_sign" - } else { - respMeta["next_action"] = "请完成企业认证" - respMeta["target_view"] = "enterprise_auth" + "next_action": "请等待管理员审核企业信息", + "target_view": "manual_review", } // 6. 转换为响应 DTO response = s.convertToResponse(cert) @@ -379,13 +374,30 @@ func (s *CertificationApplicationServiceImpl) SubmitEnterpriseInfo( return nil, err } + // 提醒管理员处理待审核申请(配置企业微信 Webhook 时生效) + if s.wechatWorkService != nil { + contactPhone := cmd.LegalPersonPhone + if strings.TrimSpace(cmd.AuthorizedRepPhone) != "" { + contactPhone = fmt.Sprintf("法人 %s;授权代表 %s", cmd.LegalPersonPhone, cmd.AuthorizedRepPhone) + } else { + contactPhone = fmt.Sprintf("%s(法人)", cmd.LegalPersonPhone) + } + _ = s.wechatWorkService.SendCertificationNotification(ctx, "pending_manual_review", map[string]interface{}{ + "company_name": cmd.CompanyName, + "legal_person_name": cmd.LegalPersonName, + "authorized_rep_name": cmd.AuthorizedRepName, + "contact_phone": contactPhone, + "api_usage": cmd.APIUsage, + "submit_at": record.SubmitAt.Format("2006-01-02 15:04:05"), + }) + } + s.logger.Info("企业信息提交成功", zap.String("user_id", cmd.UserID)) return response, nil } // 审核状态检查(步骤二) // 规则:企业信息提交成功后进入待审核;审核通过后才允许进行企业认证确认(ConfirmAuth)。 -// 当前暂时跳过人工审核(待审核状态视为通过);启用审核时恢复对 StatusInfoPendingReview 返回错误。 func (s *CertificationApplicationServiceImpl) checkAuditStatus(ctx context.Context, cert *entities.Certification) error { switch cert.Status { case enums.StatusInfoSubmitted, @@ -395,9 +407,7 @@ func (s *CertificationApplicationServiceImpl) checkAuditStatus(ctx context.Conte enums.StatusCompleted: return nil case enums.StatusInfoPendingReview: - // 暂时跳过人工审核:待审核状态视为通过,后续启用审核时还原为 return fmt.Errorf("企业信息已提交,正在审核中") - s.logger.Info("跳过人工审核状态检查", zap.String("user_id", cert.UserID)) - return nil + return fmt.Errorf("企业信息已提交,正在审核中") case enums.StatusInfoRejected: return fmt.Errorf("企业信息审核未通过") default: @@ -979,7 +989,15 @@ func (s *CertificationApplicationServiceImpl) AdminApproveSubmitRecord(ctx conte if err := cert.ApproveEnterpriseInfoReview("", "", adminID); err != nil { return fmt.Errorf("更新认证状态失败: %w", err) } - return s.completeEnterpriseVerification(ctx, cert, cert.UserID, record.CompanyName, record.LegalPersonName) + if err := s.completeEnterpriseVerification(ctx, cert, cert.UserID, record.CompanyName, record.LegalPersonName); err != nil { + return err + } + record.MarkManualApproved(adminID, remark) + if err := s.enterpriseInfoSubmitRecordService.Save(ctx, record); err != nil { + return fmt.Errorf("保存企业信息提交记录失败: %w", err) + } + s.logger.Info("管理员审核通过企业信息", zap.String("record_id", recordID), zap.String("admin_id", adminID)) + return nil } if err := cert.ApproveEnterpriseInfoReview(authURL.AuthShortURL, authURL.AuthFlowID, adminID); err != nil { return fmt.Errorf("更新认证状态失败: %w", err) @@ -987,6 +1005,10 @@ func (s *CertificationApplicationServiceImpl) AdminApproveSubmitRecord(ctx conte if err := s.aggregateService.SaveCertification(ctx, cert); err != nil { return fmt.Errorf("保存认证信息失败: %w", err) } + record.MarkManualApproved(adminID, remark) + if err := s.enterpriseInfoSubmitRecordService.Save(ctx, record); err != nil { + return fmt.Errorf("保存企业信息提交记录失败: %w", err) + } s.logger.Info("管理员审核通过企业信息", zap.String("record_id", recordID), zap.String("admin_id", adminID)) return nil } @@ -1025,6 +1047,10 @@ func (s *CertificationApplicationServiceImpl) AdminRejectSubmitRecord(ctx contex if err := s.aggregateService.SaveCertification(ctx, cert); err != nil { return fmt.Errorf("保存认证信息失败: %w", err) } + record.MarkManualRejected(adminID, remark) + if err := s.enterpriseInfoSubmitRecordService.Save(ctx, record); err != nil { + return fmt.Errorf("保存企业信息提交记录失败: %w", err) + } s.logger.Info("管理员审核拒绝企业信息", zap.String("record_id", recordID), zap.String("admin_id", adminID)) return nil } @@ -1071,7 +1097,15 @@ func (s *CertificationApplicationServiceImpl) AdminTransitionCertificationStatus if err := cert.ApproveEnterpriseInfoReview("", "", cmd.AdminID); err != nil { return fmt.Errorf("更新认证状态失败: %w", err) } - return s.completeEnterpriseVerification(ctx, cert, cert.UserID, record.CompanyName, record.LegalPersonName) + if err := s.completeEnterpriseVerification(ctx, cert, cert.UserID, record.CompanyName, record.LegalPersonName); err != nil { + return err + } + record.MarkManualApproved(cmd.AdminID, cmd.Remark) + if err := s.enterpriseInfoSubmitRecordService.Save(ctx, record); err != nil { + return fmt.Errorf("保存企业信息提交记录失败: %w", err) + } + s.logger.Info("管理端变更认证状态为通过", zap.String("user_id", cmd.UserID), zap.String("admin_id", cmd.AdminID)) + return nil } if err := cert.ApproveEnterpriseInfoReview(authURL.AuthShortURL, authURL.AuthFlowID, cmd.AdminID); err != nil { return fmt.Errorf("更新认证状态失败: %w", err) @@ -1079,6 +1113,10 @@ func (s *CertificationApplicationServiceImpl) AdminTransitionCertificationStatus if err := s.aggregateService.SaveCertification(ctx, cert); err != nil { return fmt.Errorf("保存认证信息失败: %w", err) } + record.MarkManualApproved(cmd.AdminID, cmd.Remark) + if err := s.enterpriseInfoSubmitRecordService.Save(ctx, record); err != nil { + return fmt.Errorf("保存企业信息提交记录失败: %w", err) + } s.logger.Info("管理端变更认证状态为通过", zap.String("user_id", cmd.UserID), zap.String("admin_id", cmd.AdminID)) return nil case string(enums.StatusInfoRejected): @@ -1096,6 +1134,10 @@ func (s *CertificationApplicationServiceImpl) AdminTransitionCertificationStatus if err := s.aggregateService.SaveCertification(ctx, cert); err != nil { return fmt.Errorf("保存认证信息失败: %w", err) } + record.MarkManualRejected(cmd.AdminID, cmd.Remark) + if err := s.enterpriseInfoSubmitRecordService.Save(ctx, record); err != nil { + return fmt.Errorf("保存企业信息提交记录失败: %w", err) + } s.logger.Info("管理端变更认证状态为拒绝", zap.String("user_id", cmd.UserID), zap.String("admin_id", cmd.AdminID)) return nil default: @@ -1557,7 +1599,7 @@ func (s *CertificationApplicationServiceImpl) AddStatusMetadata(ctx context.Cont metadata := make(map[string]interface{}) metadata = cert.GetDataByStatus() switch cert.Status { - case enums.StatusPending, enums.StatusInfoSubmitted, enums.StatusEnterpriseVerified: + case enums.StatusPending, enums.StatusInfoPendingReview, enums.StatusInfoRejected, enums.StatusInfoSubmitted, enums.StatusEnterpriseVerified: record, err := s.enterpriseInfoSubmitRecordRepo.FindLatestByUserID(ctx, cert.UserID) if err == nil && record != nil { enterpriseInfo := map[string]interface{}{ @@ -1567,6 +1609,7 @@ func (s *CertificationApplicationServiceImpl) AddStatusMetadata(ctx context.Cont "enterprise_address": record.EnterpriseAddress, "legal_person_phone": record.LegalPersonPhone, "legal_person_id": record.LegalPersonID, + "submit_at": record.SubmitAt.Format(time.RFC3339), } metadata["enterprise_info"] = enterpriseInfo } diff --git a/internal/infrastructure/external/notification/wechat_work_service.go b/internal/infrastructure/external/notification/wechat_work_service.go index 750a308..61f6785 100644 --- a/internal/infrastructure/external/notification/wechat_work_service.go +++ b/internal/infrastructure/external/notification/wechat_work_service.go @@ -134,6 +134,8 @@ func (s *WeChatWorkService) SendCertificationNotification(ctx context.Context, n switch notificationType { case "new_application": return s.sendNewApplicationNotification(ctx, data) + case "pending_manual_review": + return s.sendPendingManualReviewNotification(ctx, data) case "ocr_success": return s.sendOCRSuccessNotification(ctx, data) case "ocr_failed": @@ -177,6 +179,45 @@ func (s *WeChatWorkService) sendNewApplicationNotification(ctx context.Context, return s.SendMarkdownMessage(ctx, content) } +// sendPendingManualReviewNotification 用户已提交企业信息,待管理员人工审核(三真审核前序步骤) +func (s *WeChatWorkService) sendPendingManualReviewNotification(ctx context.Context, data map[string]interface{}) error { + companyName := fmt.Sprint(data["company_name"]) + legalPersonName := fmt.Sprint(data["legal_person_name"]) + authorizedRepName := fmt.Sprint(data["authorized_rep_name"]) + contactPhone := fmt.Sprint(data["contact_phone"]) + apiUsage := fmt.Sprint(data["api_usage"]) + submitAt := fmt.Sprint(data["submit_at"]) + + if authorizedRepName == "" || authorizedRepName == "" { + authorizedRepName = "—" + } + if apiUsage == "" || apiUsage == "" { + apiUsage = "—" + } + if contactPhone == "" || contactPhone == "" { + contactPhone = "—" + } + + content := fmt.Sprintf(`## 【天远API】📋 企业信息待人工审核 + +**企业名称**: %s +**法人**: %s +**授权申请人**: %s +**联系电话**: %s +**应用场景说明**: %s +**提交时间**: %s + +> 请管理员登录后台 **企业审核** 通过审核后,用户方可进行 e签宝企业认证。`, + companyName, + legalPersonName, + authorizedRepName, + contactPhone, + apiUsage, + submitAt) + + return s.SendMarkdownMessage(ctx, content) +} + // sendOCRSuccessNotification 发送OCR识别成功通知 func (s *WeChatWorkService) sendOCRSuccessNotification(ctx context.Context, data map[string]interface{}) error { companyName := data["company_name"].(string) @@ -391,14 +432,13 @@ func (s *WeChatWorkService) sendMessage(ctx context.Context, message map[string] isTimeout = true } else if netErr, ok := err.(interface{ Timeout() bool }); ok && netErr.Timeout() { isTimeout = true - } else if errStr := err.Error(); - errStr == "context deadline exceeded" || - errStr == "timeout" || - errStr == "Client.Timeout exceeded" || - errStr == "net/http: request canceled" { + } else if errStr := err.Error(); errStr == "context deadline exceeded" || + errStr == "timeout" || + errStr == "Client.Timeout exceeded" || + errStr == "net/http: request canceled" { isTimeout = true } - + errorMsg := "发送请求失败" if isTimeout { errorMsg = "发送请求超时" diff --git a/internal/shared/middleware/daily_rate_limit.go b/internal/shared/middleware/daily_rate_limit.go index 122b92a..64fc403 100644 --- a/internal/shared/middleware/daily_rate_limit.go +++ b/internal/shared/middleware/daily_rate_limit.go @@ -113,6 +113,12 @@ func (m *DailyRateLimitMiddleware) Handle() gin.HandlerFunc { c.Next() return } + // 开发环境debug模式下跳过 + if m.config.Development.Debug { + m.logger.Info("开发环境debug模式下跳过每日限流", zap.String("path", c.Request.URL.Path)) + c.Next() + return + } // 检查是否在排除域名中 host := c.Request.Host