Files
MeBox/internal/service/reader/group.go
T
truewhile 5acfa6292a 优化
2026-10-02 11:05:59 +08:00

112 lines
3.5 KiB
Go

package reader
import (
"context"
"fmt"
"github.com/truewhile/MeBox/internal/model"
)
// 书架分组(对应 legado BookGroup;存储见 model.ReaderBookGroups)。
//
// 语义与影视模块的媒体库标签一致:整份替换、一本书只归一个组、组内顺序即展示顺序。
// 额外的两条收敛放在服务层,因为它们需要知道书籍是否存在:
// - 只保留该用户书架上的书(书被移出书架后分组里不留死 ID);
// - 一本书只归一个组(越靠前的分组优先)。
// 空分组会保留:用户可能就是先建好分组再往里放书。
// GetBookGroups 读用户的书架分组。没有分组时返回空切片(非 nil),方便前端直接遍历。
func (s *ReaderService) GetBookGroups(ctx context.Context, userID string) ([]model.BookGroupSet, error) {
if userID == "" {
return []model.BookGroupSet{}, nil
}
row, err := s.repo.GetBookGroups(ctx, userID)
if err != nil {
return nil, err
}
groups := model.NormalizeBookGroups(row.DecodeBookGroups())
if len(groups) == 0 {
return []model.BookGroupSet{}, nil
}
owned, err := s.ownedBookIDs(ctx, userID)
if err != nil {
return nil, err
}
return filterBookGroups(groups, owned), nil
}
// SetBookGroups 覆盖保存用户的书架分组,落库前做过收敛与归属过滤。
func (s *ReaderService) SetBookGroups(ctx context.Context, userID string, groups []model.BookGroupSet) ([]model.BookGroupSet, error) {
if userID == "" {
return nil, fmt.Errorf("缺少用户信息")
}
owned, err := s.ownedBookIDs(ctx, userID)
if err != nil {
return nil, err
}
normalized := dedupeBookGroups(filterBookGroups(model.NormalizeBookGroups(groups), owned))
raw, err := model.EncodeBookGroups(normalized)
if err != nil {
return nil, err
}
if err := s.repo.SaveBookGroups(ctx, &model.ReaderBookGroups{UserID: userID, Groups: raw}); err != nil {
return nil, err
}
if len(normalized) == 0 {
return []model.BookGroupSet{}, nil
}
return normalized, nil
}
// ownedBookIDs 返回该用户书架上的书籍 ID 集合。
func (s *ReaderService) ownedBookIDs(ctx context.Context, userID string) (map[string]struct{}, error) {
books, err := s.repo.ListBooks(ctx, userID)
if err != nil {
return nil, err
}
out := make(map[string]struct{}, len(books))
for i := range books {
out[books[i].ID] = struct{}{}
}
return out, nil
}
// filterBookGroups 只保留 owned 里的书籍 ID;空分组原样保留。
func filterBookGroups(groups []model.BookGroupSet, owned map[string]struct{}) []model.BookGroupSet {
if len(groups) == 0 {
return nil
}
out := make([]model.BookGroupSet, 0, len(groups))
for _, group := range groups {
ids := make([]string, 0, len(group.BookIDs))
for _, id := range group.BookIDs {
if _, ok := owned[id]; ok {
ids = append(ids, id)
}
}
out = append(out, model.BookGroupSet{Name: group.Name, BookIDs: ids})
}
return out
}
// dedupeBookGroups 收敛成「一本书只归一个组」:越靠前的分组优先,后面的重复项被移除。
func dedupeBookGroups(groups []model.BookGroupSet) []model.BookGroupSet {
if len(groups) == 0 {
return nil
}
claimed := make(map[string]struct{})
out := make([]model.BookGroupSet, 0, len(groups))
for _, group := range groups {
ids := make([]string, 0, len(group.BookIDs))
for _, id := range group.BookIDs {
if _, ok := claimed[id]; ok {
continue
}
claimed[id] = struct{}{}
ids = append(ids, id)
}
out = append(out, model.BookGroupSet{Name: group.Name, BookIDs: ids})
}
return out
}