This commit is contained in:
Mrx
2026-04-02 12:46:42 +08:00
parent 130f49fb9d
commit e9fe7ac303
3 changed files with 154 additions and 65 deletions

View File

@@ -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
}

View File

@@ -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 == "<nil>" {
authorizedRepName = "—"
}
if apiUsage == "" || apiUsage == "<nil>" {
apiUsage = "—"
}
if contactPhone == "" || contactPhone == "<nil>" {
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,11 +432,10 @@ 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
}

View File

@@ -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