package wecom import ( "context" "time" "github.com/bytedance/sonic" "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" "github.com/break/junhong_cmp_fiber/pkg/errors" "github.com/break/junhong_cmp_fiber/pkg/middleware" ) // DirectoryMember 是企微 Adapter 交给 Application 的最小成员快照。 type DirectoryMember struct { UserID string Name string DepartmentIDs []int64 } // DirectoryProvider 定义拉取应用可见成员的外部端口。 type DirectoryProvider interface { ListVisibleMembers(ctx context.Context, applicationID uint) ([]DirectoryMember, error) } // MemberRepository 定义可见成员快照同步和分页查询边界。 type MemberRepository interface { ReplaceVisible(ctx context.Context, 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 { applications interface { GetEnabled(ctx context.Context, applicationID uint) (*model.WeComApplication, error) } provider DirectoryProvider members MemberRepository now func() time.Time } // NewDirectoryService 创建企业微信通讯录同步用例。 func NewDirectoryService(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} } // 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 { return nil, errors.New(errors.CodeServiceUnavailable, "企业微信通讯录服务未配置") } if !canManageWeComDirectory(ctx) { return nil, errors.New(errors.CodeForbidden) } application, err := s.applications.GetEnabled(ctx, applicationID) if err != nil { return nil, err } remoteMembers, err := s.provider.ListVisibleMembers(ctx, applicationID) if err != nil { return nil, err } syncedAt := s.now().UTC() members := make([]model.WeComMember, 0, len(remoteMembers)) for _, member := range remoteMembers { departments, err := sonic.Marshal(member.DepartmentIDs) if err != nil { return nil, errors.Wrap(errors.CodeInternalError, err, "序列化企业微信成员部门失败") } members = append(members, model.WeComMember{ ApplicationID: applicationID, CorpID: application.CorpID, UserID: member.UserID, Name: member.Name, DepartmentIDs: departments, Visible: true, SyncedAt: syncedAt, CreatedAt: syncedAt, UpdatedAt: syncedAt, }) } if err := s.members.ReplaceVisible(ctx, applicationID, members, syncedAt); err != nil { return nil, err } return &dto.WeComMemberSyncResponse{ApplicationID: applicationID, SyncedCount: len(members), SyncedAt: syncedAt}, nil } // 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 { return nil, errors.New(errors.CodeServiceUnavailable, "企业微信通讯录服务未配置") } if !canManageWeComDirectory(ctx) { return nil, errors.New(errors.CodeForbidden) } if request.Page <= 0 { request.Page = constants.DefaultPage } if request.PageSize <= 0 { request.PageSize = constants.DefaultPageSize } if request.PageSize > constants.MaxPageSize { return nil, errors.New(errors.CodeInvalidParam) } if _, err := s.applications.GetEnabled(ctx, applicationID); err != nil { return nil, err } members, total, err := s.members.ListVisible(ctx, applicationID, request.Page, request.PageSize, request.Keyword) if err != nil { return nil, err } items := make([]dto.WeComMemberResponse, 0, len(members)) for _, member := range members { var departmentIDs []int64 if len(member.DepartmentIDs) > 0 { if err := sonic.Unmarshal(member.DepartmentIDs, &departmentIDs); err != nil { return nil, errors.Wrap(errors.CodeInternalError, err, "解析企业微信成员部门失败") } } items = append(items, dto.WeComMemberResponse{ ApplicationID: member.ApplicationID, CorpID: member.CorpID, UserID: member.UserID, Name: member.Name, DepartmentIDs: departmentIDs, SyncedAt: member.SyncedAt, }) } return &dto.WeComMemberListResponse{Items: items, Total: total, Page: request.Page, PageSize: request.PageSize}, nil } func canManageWeComDirectory(ctx context.Context) bool { userType := middleware.GetUserTypeFromContext(ctx) return userType == constants.UserTypeSuperAdmin || userType == constants.UserTypePlatform }