package wecom import ( "context" "crypto/sha256" "encoding/hex" "strings" "github.com/bytedance/sonic" "gorm.io/gorm" approvalapp "github.com/break/junhong_cmp_fiber/internal/application/approval" wecomapp "github.com/break/junhong_cmp_fiber/internal/application/wecom" "github.com/break/junhong_cmp_fiber/internal/model" "github.com/break/junhong_cmp_fiber/pkg/constants" "github.com/break/junhong_cmp_fiber/pkg/errors" ) // ApprovalProvider 实现通用 Approval Port 的企微准备与渠道上下文写入。 type ApprovalProvider struct { db *gorm.DB scenes *SceneRepository applications *ApplicationRepository members *MemberRepository templates wecomapp.TemplateProvider } type approvalPreparationContext struct { ApplicationID uint `json:"application_id"` BusinessType string `json:"business_type"` TemplateID string `json:"template_id"` CreatorUserID string `json:"creator_userid"` CreatorName string `json:"creator_name"` CreatorSource string `json:"creator_source"` ControlMapping []map[string]any `json:"control_mapping"` TemplateSnapshot wecomapp.TemplateDefinition `json:"template_snapshot"` TemplateFingerprint string `json:"template_fingerprint"` } // NewApprovalProvider 创建企业微信通用审批渠道 Adapter。 func NewApprovalProvider(db *gorm.DB, scenes *SceneRepository, applications *ApplicationRepository, members *MemberRepository, templates wecomapp.TemplateProvider) *ApprovalProvider { return &ApprovalProvider{db: db, scenes: scenes, applications: applications, members: members, templates: templates} } // Prepare 在业务事务前校验场景、模板和可用的企微发起人。 func (p *ApprovalProvider) Prepare(ctx context.Context, request approvalapp.PrepareRequest) (approvalapp.ProviderPreparation, error) { if p == nil || p.db == nil || p.scenes == nil || p.applications == nil || p.members == nil || p.templates == nil { return approvalapp.ProviderPreparation{}, errors.New(errors.CodeServiceUnavailable, "企业微信审批 Adapter 未配置") } scene, err := p.scenes.GetEnabled(ctx, strings.TrimSpace(request.BusinessType)) if err != nil { return approvalapp.ProviderPreparation{}, err } application, err := p.applications.GetEnabled(ctx, scene.ApplicationID) if err != nil { return approvalapp.ProviderPreparation{}, err } creator, source, err := p.resolveCreator(ctx, application, request.SubmitterAccountID) if err != nil { return approvalapp.ProviderPreparation{}, err } definition, err := p.templates.GetTemplateDetail(ctx, scene.ApplicationID, scene.TemplateID) if err != nil { return approvalapp.ProviderPreparation{}, err } fingerprint, err := templateFingerprint(definition) if err != nil { return approvalapp.ProviderPreparation{}, err } if fingerprint != scene.TemplateFingerprint { return approvalapp.ProviderPreparation{}, errors.New(errors.CodeInvalidStatus, "企业微信审批模板已变化,请管理员重新校验场景配置") } var mapping []map[string]any if err := sonic.Unmarshal(scene.ControlMapping, &mapping); err != nil || len(mapping) == 0 { return approvalapp.ProviderPreparation{}, errors.New(errors.CodeInvalidStatus, "企业微信审批控件映射无效") } channelContext, err := sonic.Marshal(approvalPreparationContext{ ApplicationID: scene.ApplicationID, BusinessType: scene.BusinessType, TemplateID: scene.TemplateID, CreatorUserID: creator.UserID, CreatorName: creator.Name, CreatorSource: source, ControlMapping: mapping, TemplateSnapshot: definition, TemplateFingerprint: fingerprint, }) if err != nil { return approvalapp.ProviderPreparation{}, errors.Wrap(errors.CodeInternalError, err, "编码企业微信审批准备结果失败") } return approvalapp.ProviderPreparation{Provider: constants.IntegrationProviderWeCom, ChannelContext: channelContext}, nil } // CreateContextInTx 在调用方事务中保存不含凭据的企微提交快照。 func (p *ApprovalProvider) CreateContextInTx(ctx context.Context, tx *gorm.DB, preparation approvalapp.ProviderPreparation, instanceID uint) error { if p == nil || tx == nil || preparation.Provider != constants.IntegrationProviderWeCom || instanceID == 0 { return errors.New(errors.CodeInvalidParam, "企业微信审批渠道上下文参数无效") } var prepared approvalPreparationContext if err := sonic.Unmarshal(preparation.ChannelContext, &prepared); err != nil { return errors.Wrap(errors.CodeInvalidParam, err, "解析企业微信审批准备结果失败") } mappingJSON, err := sonic.Marshal(prepared.ControlMapping) if err != nil { return errors.Wrap(errors.CodeInternalError, err, "编码企业微信审批控件映射失败") } templateJSON, err := sonic.Marshal(prepared.TemplateSnapshot) if err != nil { return errors.Wrap(errors.CodeInternalError, err, "编码企业微信审批模板快照失败") } record := model.WeComApprovalContext{ ApprovalInstanceID: instanceID, ApplicationID: prepared.ApplicationID, BusinessType: prepared.BusinessType, TemplateID: prepared.TemplateID, CreatorUserID: prepared.CreatorUserID, CreatorName: prepared.CreatorName, CreatorSource: prepared.CreatorSource, ControlMapping: mappingJSON, TemplateSnapshot: templateJSON, TemplateFingerprint: prepared.TemplateFingerprint, SubmissionStatus: constants.WeComSubmissionStatusReady, } if err := tx.WithContext(ctx).Create(&record).Error; err != nil { return errors.Wrap(errors.CodeDatabaseError, err, "创建企业微信审批渠道上下文失败") } return nil } // resolveCreator 保留真实业务提交人,同时按账号类型选择企微本人或应用默认成员作为接口发起人。 func (p *ApprovalProvider) resolveCreator(ctx context.Context, application *model.WeComApplication, accountID uint) (*model.WeComMember, string, error) { var account model.Account if err := p.db.WithContext(ctx).Select("id", "user_type", "wecom_corp_id", "wecom_userid").Where("id = ?", accountID).First(&account).Error; err != nil { if err == gorm.ErrRecordNotFound { return nil, "", errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在") } return nil, "", errors.Wrap(errors.CodeDatabaseError, err, "查询审批真实提交人失败") } useBoundCreator := account.UserType == constants.UserTypeSuperAdmin || account.UserType == constants.UserTypePlatform if useBoundCreator && strings.EqualFold(account.WeComCorpID, application.CorpID) && strings.TrimSpace(account.WeComUserID) != "" { member, err := p.members.GetVisible(ctx, application.ID, account.WeComUserID) if err == nil { return member, constants.WeComCreatorSourceBound, nil } } if strings.TrimSpace(application.DefaultCreatorUserID) == "" { return nil, "", errors.New(errors.CodeInvalidStatus, "企业微信应用尚未配置默认审批发起人") } member, err := p.members.GetVisible(ctx, application.ID, application.DefaultCreatorUserID) if err != nil { return nil, "", errors.New(errors.CodeInvalidStatus, "企业微信默认审批发起人已不可用,请管理员重新配置") } return member, constants.WeComCreatorSourceDefault, nil } func templateFingerprint(definition wecomapp.TemplateDefinition) (string, error) { snapshot, err := sonic.Marshal(definition) if err != nil { return "", errors.Wrap(errors.CodeInternalError, err, "编码企业微信模板快照失败") } hash := sha256.Sum256(snapshot) return hex.EncodeToString(hash[:]), nil } var _ approvalapp.ProviderPort = (*ApprovalProvider)(nil)