All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m26s
153 lines
7.6 KiB
Go
153 lines
7.6 KiB
Go
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)
|