owpengram-server/internal/store/memory/media_search.go

198 lines
5.4 KiB
Go

package memory
import (
"context"
"sort"
"telesrv/internal/domain"
)
// 共享媒体标签页读路径(memory 实现):直接扫内存消息按 domain.ClassifyMediaCategories 分类过滤,
// 无需索引表(数据量小)。分类真值与 postgres 写路径/回填同源。
func mediaCategorySet(cats []domain.MediaCategory) map[domain.MediaCategory]bool {
set := make(map[domain.MediaCategory]bool, len(cats))
for _, c := range cats {
if c != domain.MediaCategoryNone {
set[c] = true
}
}
return set
}
func mediaCategoryMatches(media *domain.MessageMedia, entities []domain.MessageEntity, set map[domain.MediaCategory]bool) bool {
for _, c := range domain.ClassifyMediaCategories(media, entities) {
if set[c] {
return true
}
}
return false
}
// pageMediaIDs 把全部匹配 id 按 newest-first 分页(返回本页 id + 满足 max/min 的总数)。
func pageMediaIDs(ids []int, req domain.MediaSearchRequest) ([]int, int) {
sort.Sort(sort.Reverse(sort.IntSlice(ids)))
inRange := make([]int, 0, len(ids))
for _, id := range ids {
if req.MaxID != 0 && id > req.MaxID {
continue
}
if req.MinID != 0 && id < req.MinID {
continue
}
inRange = append(inRange, id)
}
count := len(inRange)
page := make([]int, 0, len(inRange))
for _, id := range inRange {
if req.OffsetID != 0 && id >= req.OffsetID {
continue
}
page = append(page, id)
}
off := req.AddOffset
if off < 0 {
off = 0
}
if off > len(page) {
off = len(page)
}
page = page[off:]
limit := req.Limit
if limit == 0 {
return nil, count
}
if limit < 0 || limit > 100 {
limit = 100
}
if len(page) > limit {
page = page[:limit]
}
return page, count
}
// SearchPrivateMedia 实现 store.MessageStore。
func (s *MessageStore) SearchPrivateMedia(ctx context.Context, ownerUserID, peerID int64, req domain.MediaSearchRequest) (domain.MessageList, error) {
set := mediaCategorySet(req.Categories)
if ownerUserID == 0 || peerID == 0 || len(set) == 0 {
return domain.MessageList{}, nil
}
s.mu.RLock()
matched := make([]int, 0, len(s.m[ownerUserID]))
for _, msg := range s.m[ownerUserID] {
if msg.Peer.Type != domain.PeerTypeUser || msg.Peer.ID != peerID {
continue
}
if mediaCategoryMatches(msg.Media, msg.Entities, set) {
matched = append(matched, msg.ID)
}
}
s.mu.RUnlock()
ids, count := pageMediaIDs(matched, req)
if req.HasKnownCount {
count = req.KnownCount
}
list, err := s.GetByIDs(ctx, ownerUserID, ids)
if err != nil {
return domain.MessageList{}, err
}
list.Count = count
return list, nil
}
// CountPrivateMediaCategories 实现 store.MessageStore。
func (s *MessageStore) CountPrivateMediaCategories(_ context.Context, ownerUserID, peerID int64) (domain.MediaCategoryCounts, error) {
if ownerUserID == 0 || peerID == 0 {
return domain.MediaCategoryCounts{}, nil
}
s.mu.RLock()
defer s.mu.RUnlock()
out := domain.MediaCategoryCounts{}
for _, msg := range s.m[ownerUserID] {
if msg.Peer.Type != domain.PeerTypeUser || msg.Peer.ID != peerID {
continue
}
for _, category := range domain.ClassifyMediaCategories(msg.Media, msg.Entities) {
if category != domain.MediaCategoryNone {
out[category]++
}
}
}
return out, nil
}
// SearchChannelMedia 实现 store.ChannelStore。
func (s *ChannelStore) SearchChannelMedia(ctx context.Context, viewerUserID, channelID int64, req domain.MediaSearchRequest) (domain.ChannelHistory, error) {
set := mediaCategorySet(req.Categories)
if viewerUserID == 0 || channelID == 0 || len(set) == 0 {
return domain.ChannelHistory{}, nil
}
s.mu.RLock()
_, member, err := s.channelAndMemberLocked(viewerUserID, channelID)
if err != nil {
s.mu.RUnlock()
return domain.ChannelHistory{}, err
}
matched := make([]int, 0, len(s.messages[channelID]))
for _, msg := range s.messages[channelID] {
if msg.Deleted || msg.ID <= member.AvailableMinID {
continue
}
if mediaCategoryMatches(msg.Media, msg.Entities, set) {
matched = append(matched, msg.ID)
}
}
s.mu.RUnlock()
ids, count := pageMediaIDs(matched, req)
if req.HasKnownCount {
count = req.KnownCount
}
hist, err := s.GetChannelMessages(ctx, viewerUserID, channelID, ids)
if err != nil {
return domain.ChannelHistory{}, err
}
hist.Messages = reorderChannelMessagesByMediaOrder(hist.Messages, ids)
hist.Count = count
return hist, nil
}
// CountChannelMediaCategories 实现 store.ChannelStore。
func (s *ChannelStore) CountChannelMediaCategories(_ context.Context, viewerUserID, channelID int64) (domain.MediaCategoryCounts, error) {
if viewerUserID == 0 || channelID == 0 {
return domain.MediaCategoryCounts{}, nil
}
s.mu.RLock()
defer s.mu.RUnlock()
_, member, err := s.channelAndMemberLocked(viewerUserID, channelID)
if err != nil {
return domain.MediaCategoryCounts{}, err
}
out := domain.MediaCategoryCounts{}
for _, msg := range s.messages[channelID] {
if msg.Deleted || msg.ID <= member.AvailableMinID {
continue
}
for _, category := range domain.ClassifyMediaCategories(msg.Media, msg.Entities) {
if category != domain.MediaCategoryNone {
out[category]++
}
}
}
return out, nil
}
func reorderChannelMessagesByMediaOrder(msgs []domain.ChannelMessage, order []int) []domain.ChannelMessage {
byID := make(map[int]domain.ChannelMessage, len(msgs))
for _, m := range msgs {
byID[m.ID] = m
}
out := make([]domain.ChannelMessage, 0, len(order))
for _, id := range order {
if m, ok := byID[id]; ok {
out = append(out, m)
}
}
return out
}