All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m26s
160 lines
5.8 KiB
Go
160 lines
5.8 KiB
Go
package wecom
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/bytedance/sonic"
|
|
"github.com/hibiken/asynq"
|
|
|
|
approvalapp "github.com/break/junhong_cmp_fiber/internal/application/approval"
|
|
"github.com/break/junhong_cmp_fiber/internal/infrastructure/integrationlog"
|
|
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
|
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
|
)
|
|
|
|
// ApprovalDetailTaskHandler 拉取企微审批详情并翻译为现有标准决策。
|
|
type ApprovalDetailTaskHandler struct {
|
|
details *ApprovalDetailClient
|
|
contexts *ApprovalContextRepository
|
|
decisions *approvalapp.SyncDecisionService
|
|
integration TokenIntegrationLog
|
|
}
|
|
|
|
// NewApprovalDetailTaskHandler 创建企业微信审批详情同步任务 Handler。
|
|
func NewApprovalDetailTaskHandler(details *ApprovalDetailClient, contexts *ApprovalContextRepository, decisions *approvalapp.SyncDecisionService, integration TokenIntegrationLog) *ApprovalDetailTaskHandler {
|
|
return &ApprovalDetailTaskHandler{details: details, contexts: contexts, decisions: decisions, integration: integration}
|
|
}
|
|
|
|
// Handle 处理回调或后续轮询提交的详情同步任务。
|
|
func (h *ApprovalDetailTaskHandler) Handle(ctx context.Context, task *asynq.Task) error {
|
|
if h == nil || h.details == nil || h.contexts == nil || h.decisions == nil || h.integration == nil {
|
|
return errors.New(errors.CodeServiceUnavailable, "企业微信审批详情同步任务未配置")
|
|
}
|
|
var payload ApprovalDetailSyncTask
|
|
if err := sonic.Unmarshal(task.Payload(), &payload); err != nil || payload.ApplicationID == 0 || strings.TrimSpace(payload.SPNo) == "" {
|
|
return errors.New(errors.CodeInvalidParam, "企业微信审批详情同步任务载荷无效")
|
|
}
|
|
if !validApprovalSyncSource(payload.Source) {
|
|
return errors.New(errors.CodeInvalidParam, "企业微信审批详情同步来源无效")
|
|
}
|
|
detail, err := h.details.Get(ctx, payload.ApplicationID, payload.SPNo)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
record, err := h.contexts.FindBySPNo(ctx, payload.ApplicationID, payload.SPNo)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if record == nil {
|
|
record, err = h.recoverUnknownCallback(ctx, payload, detail)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if record == nil {
|
|
return h.completeIgnoredCallback(ctx, payload)
|
|
}
|
|
}
|
|
if err := h.contexts.SaveLatestDetail(ctx, record.Instance.ID, detail.SPStatus, detail.Snapshot); err != nil {
|
|
return err
|
|
}
|
|
decisions := weComDecisions(detail.SPStatus)
|
|
for _, decision := range decisions {
|
|
if _, err := h.decisions.Execute(ctx, approvalapp.SyncDecisionCommand{
|
|
InstanceID: record.Instance.ID, Decision: decision, DecisionSnapshot: detail.Snapshot, Source: payload.Source,
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if strings.TrimSpace(payload.IntegrationID) != "" {
|
|
_, err = h.integration.Complete(ctx, payload.IntegrationID, integrationlog.Completion{
|
|
Result: constants.IntegrationResultCompleted, ProviderCode: "processed",
|
|
ProviderMessage: "企业微信审批回调已完成权威详情同步",
|
|
ResponseSummary: map[string]any{"sp_no": payload.SPNo, "sp_status": detail.SPStatus, "decisions": decisions},
|
|
DurationMS: 0, StateChanged: len(decisions) > 0,
|
|
})
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (h *ApprovalDetailTaskHandler) recoverUnknownCallback(ctx context.Context, payload ApprovalDetailSyncTask, detail ApprovalDetail) (*ApprovalSubmissionRecord, error) {
|
|
if payload.Source != constants.ApprovalSyncSourceCallback {
|
|
return nil, nil
|
|
}
|
|
fingerprint := approvalDetailFingerprint(detail.Snapshot)
|
|
candidate, err := h.contexts.FindUniqueUnknownByFingerprint(
|
|
ctx, payload.ApplicationID, fingerprint.TemplateID, fingerprint.CreatorUserID, fingerprint.SubmittedAt,
|
|
)
|
|
if err != nil || candidate == nil {
|
|
return nil, err
|
|
}
|
|
recovered, err := h.contexts.RecoverSubmitted(ctx, candidate.InstanceID, payload.SPNo)
|
|
if err != nil || !recovered {
|
|
return nil, err
|
|
}
|
|
return h.contexts.Get(ctx, candidate.InstanceID)
|
|
}
|
|
|
|
func (h *ApprovalDetailTaskHandler) completeIgnoredCallback(ctx context.Context, payload ApprovalDetailSyncTask) error {
|
|
if strings.TrimSpace(payload.IntegrationID) == "" {
|
|
return nil
|
|
}
|
|
_, err := h.integration.Complete(ctx, payload.IntegrationID, integrationlog.Completion{
|
|
Result: constants.IntegrationResultIgnored, ProviderCode: "unrelated_approval",
|
|
ProviderMessage: "企业微信审批单未关联本系统业务,已忽略",
|
|
ResponseSummary: map[string]any{"sp_no": payload.SPNo, "ignored": true},
|
|
})
|
|
return err
|
|
}
|
|
|
|
type approvalFingerprint struct {
|
|
TemplateID string
|
|
CreatorUserID string
|
|
SubmittedAt time.Time
|
|
}
|
|
|
|
func approvalDetailFingerprint(snapshot []byte) approvalFingerprint {
|
|
var value struct {
|
|
TemplateID string `json:"template_id"`
|
|
ApplyTime int64 `json:"apply_time"`
|
|
Applyer struct {
|
|
UserID string `json:"userid"`
|
|
} `json:"applyer"`
|
|
}
|
|
if sonic.Unmarshal(snapshot, &value) != nil || value.ApplyTime <= 0 {
|
|
return approvalFingerprint{}
|
|
}
|
|
return approvalFingerprint{
|
|
TemplateID: strings.TrimSpace(value.TemplateID), CreatorUserID: strings.TrimSpace(value.Applyer.UserID),
|
|
SubmittedAt: time.Unix(value.ApplyTime, 0).UTC(),
|
|
}
|
|
}
|
|
|
|
func validApprovalSyncSource(source string) bool {
|
|
switch source {
|
|
case constants.ApprovalSyncSourceCallback, constants.ApprovalSyncSourcePolling, constants.ApprovalSyncSourceManual:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// weComDecisions 只翻译现有审批核心支持的企微标准终态;通过后撤销按领域允许顺序补齐两个事实。
|
|
func weComDecisions(status int) []string {
|
|
switch status {
|
|
case 2:
|
|
return []string{constants.ApprovalDecisionApproved}
|
|
case 3:
|
|
return []string{constants.ApprovalDecisionRejected}
|
|
case 4:
|
|
return []string{constants.ApprovalDecisionCancelled}
|
|
case 6:
|
|
return []string{constants.ApprovalDecisionApproved, constants.ApprovalDecisionRevokedAfterApproved}
|
|
case 7:
|
|
return []string{constants.ApprovalDecisionDeleted}
|
|
default:
|
|
return nil
|
|
}
|
|
}
|