Files
junhong_cmp_fiber/internal/infrastructure/wecom/approval_provider.go
break 73f5125d3d
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m26s
七月迭代短暂完结,还有很多后端的关键东西没有弄,这是一版赶时间做的东西
2026-07-25 17:06:58 +08:00

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)