七月迭代短暂完结,还有很多后端的关键东西没有弄,这是一版赶时间做的东西
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m26s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m26s
This commit is contained in:
152
internal/infrastructure/wecom/approval_provider.go
Normal file
152
internal/infrastructure/wecom/approval_provider.go
Normal file
@@ -0,0 +1,152 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user