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

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
}
}