新增代理注册申请管理
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m41s

This commit is contained in:
2026-09-21 16:51:37 +08:00
parent b063617153
commit 4a16eb0b1e
22 changed files with 927 additions and 179 deletions

View File

@@ -17,6 +17,7 @@ import (
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/errors"
"github.com/break/junhong_cmp_fiber/pkg/middleware"
)
// RegistrationResult 返回已落库的待审批注册记录与审批实例引用。
@@ -33,24 +34,86 @@ type RegistrationService struct {
db *gorm.DB
verifier VerificationCodeVerifier
approval approvalapp.Port
recovery approvalapp.RecoveryPort
audit AuditWriter
}
// NewRegistrationService 创建公开扫码注册用例。
func NewRegistrationService(
db *gorm.DB,
verifier VerificationCodeVerifier,
approval approvalapp.Port,
audit AuditWriter,
) *RegistrationService {
func NewRegistrationService(db *gorm.DB, verifier VerificationCodeVerifier, approval approvalapp.Port, audit AuditWriter) *RegistrationService {
return &RegistrationService{db: db, verifier: verifier, approval: approval, audit: audit}
}
// Register 创建待审批注册记录
// 分销码无效、上级店铺停用、上级店铺缺少启用的主账号、短信验证码无效分别返回各自的错误码与提示。
// 短信验证码只在注册记录与审批实例落库成功后消费:落库前的任何失败都不消费验证码,重试无需重新获取。
// 手机号、用户名或店铺编号与既有账号/店铺冲突时返回对应已存在错误码;
// 与其它待审批注册记录冲突时返回资源冲突错误并指明冲突字段。
// SetRecoveryPort 注入通用审批原实例恢复接缝
func (s *RegistrationService) SetRecoveryPort(recovery approvalapp.RecoveryPort) {
s.recovery = recovery
}
// RecoverApproval 恢复代理注册原审批提交事实,不创建新审批实例或业务实体。
func (s *RegistrationService) RecoverApproval(ctx context.Context, id uint) error {
userType := middleware.GetUserTypeFromContext(ctx)
if userType == constants.UserTypeEnterprise || (userType != constants.UserTypeSuperAdmin && userType != constants.UserTypePlatform && userType != constants.UserTypeAgent) {
return errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在")
}
if middleware.GetUserIDFromContext(ctx) == 0 {
return errors.New(errors.CodeUnauthorized)
}
if s == nil || s.db == nil || s.recovery == nil {
return errors.New(errors.CodeServiceUnavailable, "代理注册审批恢复能力尚未配置")
}
return s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
registration, err := lockRegistrationForUpdate(ctx, tx, id)
if err != nil {
return err
}
if registration.Status != constants.AgentDistributionRegistrationStatusPending {
return errors.New(errors.CodeConflict, "终态申请不可恢复")
}
var parent model.Shop
if err := tx.Where("id = ?", registration.ParentShopID).First(&parent).Error; err != nil || parent.Status != constants.StatusEnabled {
return errors.New(errors.CodeConflict, "上级店铺已停用,不能恢复审批")
}
if userType == constants.UserTypeAgent && (middleware.GetShopIDFromContext(ctx) == 0 || middleware.GetShopIDFromContext(ctx) != registration.ParentShopID) {
return errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在")
}
if registration.ApprovalInstanceID == nil || *registration.ApprovalInstanceID == 0 {
return errors.New(errors.CodeConflict, "注册申请未关联审批实例")
}
instanceID := *registration.ApprovalInstanceID
var instance model.ApprovalInstance
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ? AND business_type = ? AND business_id = ?", instanceID, constants.ApprovalBusinessTypeAgentDistribution, registration.ID).First(&instance).Error; err != nil {
return errors.New(errors.CodeConflict, "审批实例业务关联不一致")
}
var wc model.WeComApprovalContext
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("approval_instance_id = ? AND business_type = ?", instanceID, constants.ApprovalBusinessTypeAgentDistribution).First(&wc).Error; err != nil {
return errors.New(errors.CodeConflict, "企业微信审批上下文不存在")
}
branch := "active"
var recoveryErr error
if wc.SPNo != "" {
branch = "sp_no"
recoveryErr = s.recovery.EnqueueSubmittedSync(ctx, tx, instanceID)
} else if wc.SubmissionStatus == constants.WeComSubmissionStatusUnknown || instance.Status == constants.ApprovalStatusSubmissionUnknown {
branch = "unknown"
recoveryErr = s.recovery.EnqueueUnknownConfirm(ctx, tx, instanceID)
} else if instance.Status == constants.ApprovalStatusSubmissionFailed && wc.SubmissionStatus == constants.WeComSubmissionStatusFailed {
branch = "replay"
_, recoveryErr = s.recovery.RecoverSubmissionEvent(ctx, tx, instanceID)
} else if instance.Status == constants.ApprovalStatusSubmitting || instance.Status == constants.ApprovalStatusPending || wc.SubmissionStatus == constants.WeComSubmissionStatusReady || wc.SubmissionStatus == constants.WeComSubmissionStatusSending {
} else {
return errors.New(errors.CodeConflict, "当前审批提交状态不允许恢复")
}
if recoveryErr != nil {
return errors.Wrap(errors.CodeConflict, recoveryErr, "恢复审批提交失败")
}
if s.audit != nil {
if err := s.audit.WriteDistributionWithdrawal(ctx, tx, AuditChange{EventID: "agent-distribution:" + uintText(id) + ":recover:" + branch, ActionCode: constants.AuditActionApprovalSubmissionRecovered, Summary: "人工恢复代理注册审批提交", Registration: registration, ParentShop: &parent, BeforeData: map[string]any{"submission_status": wc.SubmissionStatus, "branch": branch}, AfterData: map[string]any{"result": "accepted"}}); err != nil {
return err
}
}
return nil
})
}
// 已通过或已驳回的终态记录不阻塞重新注册;同一关键字段的并发提交由事务级 advisory lock 串行裁决。
func (s *RegistrationService) Register(
ctx context.Context,