收口审计治理与套餐任务进展
Constraint: 在线热修前必须保存当前迭代分支全部有效代码进展 Confidence: medium Scope-risk: broad Directive: 后续修改需保持审计事件与业务事务边界一致 Tested: git diff --cached --check Not-tested: 未运行全量测试,提交用于切换分支前保存既有工作
This commit is contained in:
@@ -2,10 +2,13 @@ package wecom
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"gorm.io/gorm"
|
||||
|
||||
systemconfigapp "github.com/break/junhong_cmp_fiber/internal/application/systemconfig"
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/internal/model/dto"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
@@ -27,30 +30,32 @@ type DirectoryProvider interface {
|
||||
|
||||
// MemberRepository 定义可见成员快照同步和分页查询边界。
|
||||
type MemberRepository interface {
|
||||
ReplaceVisible(ctx context.Context, applicationID uint, members []model.WeComMember, syncedAt time.Time) error
|
||||
ReplaceVisible(ctx context.Context, tx *gorm.DB, applicationID uint, members []model.WeComMember, syncedAt time.Time) error
|
||||
ListVisible(ctx context.Context, applicationID uint, page, pageSize int, keyword string) ([]model.WeComMember, int64, error)
|
||||
}
|
||||
|
||||
// DirectoryService 同步并分页查询企业微信应用可见成员。
|
||||
type DirectoryService struct {
|
||||
db *gorm.DB
|
||||
applications interface {
|
||||
GetEnabled(ctx context.Context, applicationID uint) (*model.WeComApplication, error)
|
||||
}
|
||||
provider DirectoryProvider
|
||||
members MemberRepository
|
||||
audit systemconfigapp.AuditWriter
|
||||
now func() time.Time
|
||||
}
|
||||
|
||||
// NewDirectoryService 创建企业微信通讯录同步用例。
|
||||
func NewDirectoryService(applications interface {
|
||||
func NewDirectoryService(db *gorm.DB, applications interface {
|
||||
GetEnabled(ctx context.Context, applicationID uint) (*model.WeComApplication, error)
|
||||
}, provider DirectoryProvider, members MemberRepository) *DirectoryService {
|
||||
return &DirectoryService{applications: applications, provider: provider, members: members, now: time.Now}
|
||||
}, provider DirectoryProvider, members MemberRepository, audit systemconfigapp.AuditWriter) *DirectoryService {
|
||||
return &DirectoryService{db: db, applications: applications, provider: provider, members: members, audit: audit, now: time.Now}
|
||||
}
|
||||
|
||||
// Sync 拉取并替换指定应用当前可见成员快照。
|
||||
func (s *DirectoryService) Sync(ctx context.Context, applicationID uint) (*dto.WeComMemberSyncResponse, error) {
|
||||
if s == nil || s.applications == nil || s.provider == nil || s.members == nil || applicationID == 0 {
|
||||
if s == nil || s.db == nil || s.applications == nil || s.provider == nil || s.members == nil || s.audit == nil || applicationID == 0 {
|
||||
return nil, errors.New(errors.CodeServiceUnavailable, "企业微信通讯录服务未配置")
|
||||
}
|
||||
if !canManageWeComDirectory(ctx) {
|
||||
@@ -62,6 +67,7 @@ func (s *DirectoryService) Sync(ctx context.Context, applicationID uint) (*dto.W
|
||||
}
|
||||
remoteMembers, err := s.provider.ListVisibleMembers(ctx, applicationID)
|
||||
if err != nil {
|
||||
s.recordFailure(ctx, application, "同步企业微信应用可见成员失败")
|
||||
return nil, err
|
||||
}
|
||||
syncedAt := s.now().UTC()
|
||||
@@ -77,12 +83,46 @@ func (s *DirectoryService) Sync(ctx context.Context, applicationID uint) (*dto.W
|
||||
CreatedAt: syncedAt, UpdatedAt: syncedAt,
|
||||
})
|
||||
}
|
||||
if err := s.members.ReplaceVisible(ctx, applicationID, members, syncedAt); err != nil {
|
||||
err = s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := s.members.ReplaceVisible(ctx, tx, applicationID, members, syncedAt); err != nil {
|
||||
return err
|
||||
}
|
||||
requestID := ""
|
||||
if value := middleware.GetRequestIDFromContext(ctx); value != nil {
|
||||
requestID = *value
|
||||
}
|
||||
resourceID := fmt.Sprintf("%d", applicationID)
|
||||
after := applicationAuditSnapshot(application)
|
||||
after["synced_count"] = len(members)
|
||||
after["synced_at"] = syncedAt
|
||||
return s.audit.WriteConfigChange(ctx, tx, systemconfigapp.ChangeAudit{
|
||||
OperatorID: middleware.GetUserIDFromContext(ctx), OperationType: constants.AuditOperationWeComMembersSync,
|
||||
Description: "同步企业微信应用可见成员", ConfigKey: fmt.Sprintf("wecom.application.%d.members", applicationID),
|
||||
ResourceID: &resourceID, DisplayName: application.Name, Identity: applicationAuditIdentity(application),
|
||||
AfterData: after, RequestID: requestID, CorrelationID: requestID,
|
||||
})
|
||||
})
|
||||
if err != nil {
|
||||
s.recordFailure(ctx, application, "保存企业微信应用可见成员快照失败")
|
||||
return nil, err
|
||||
}
|
||||
return &dto.WeComMemberSyncResponse{ApplicationID: applicationID, SyncedCount: len(members), SyncedAt: syncedAt}, nil
|
||||
}
|
||||
|
||||
func (s *DirectoryService) recordFailure(ctx context.Context, application *model.WeComApplication, description string) {
|
||||
if application == nil {
|
||||
return
|
||||
}
|
||||
resourceID := fmt.Sprintf("%d", application.ID)
|
||||
recordConfigFailure(ctx, s.db, s.audit, systemconfigapp.ChangeAudit{
|
||||
OperatorID: middleware.GetUserIDFromContext(ctx), OperationType: constants.AuditOperationWeComMembersSync,
|
||||
Description: description, ConfigKey: fmt.Sprintf("wecom.application.%d.members", application.ID),
|
||||
ResourceID: &resourceID, DisplayName: application.Name, Identity: applicationAuditIdentity(application),
|
||||
BeforeData: applicationAuditSnapshot(application), Result: constants.AuditResultFailed,
|
||||
ErrorCode: fmt.Sprintf("%d", errors.CodeInternalError), ErrorSummary: description,
|
||||
})
|
||||
}
|
||||
|
||||
// List 分页返回本地最近一次同步的应用可见成员。
|
||||
func (s *DirectoryService) List(ctx context.Context, applicationID uint, request dto.WeComMemberListRequest) (*dto.WeComMemberListResponse, error) {
|
||||
if s == nil || s.applications == nil || s.members == nil || applicationID == 0 {
|
||||
|
||||
Reference in New Issue
Block a user