package document_repo import ( "context" "strconv" "strings" "time" "github.com/XingfenD/yoresee_doc/internal/model" "github.com/XingfenD/yoresee_doc/pkg/cache" "github.com/XingfenD/yoresee_doc/pkg/key" "github.com/redis/go-redis/v9" "github.com/sirupsen/logrus" "golang.org/x/sync/singleflight" "gorm.io/gorm" ) const ( subtreeCacheTTL = 5 * time.Minute subtreeEmptyTTL = 1 * time.Minute subtreeCacheMaxDepth = 3 ) var subtreeCacheSF singleflight.Group func splitPathPrefixes(path string) []string { parts := strings.Split(path, ".") out := make([]string, 0, len(parts)) for i := range parts { out = append(out, strings.Join(parts[:i+1], ".")) } return out } func getDocPathByID(tx *gorm.DB, docID int64) (string, error) { type docPath struct { Path string } var result docPath err := tx.Model(&model.Document{}). Select("path"). Where("id = ?", docID). Take(&result).Error if err != nil { return "", err } return result.Path, nil } func (r *DocumentRepository) GetPathByID(id int64) (string, error) { return getDocPathByID(r.db, id) } func (r *DocumentRepository) GetPathByIDWithTx(tx *gorm.DB, id int64) (string, error) { return getDocPathByID(tx, id) } func (r *DocumentRepository) getSubtreeVersion(ctx context.Context, path string) (int64, error) { if r.redis == nil { return 0, nil } val, err := r.redis.Get(ctx, key.KeyDocSubtreeVersion(path)).Result() if err == redis.Nil { return 0, nil } if err != nil { return 0, err } version, err := strconv.ParseInt(val, 10, 64) if err != nil { return 0, nil } return version, nil } func (r *DocumentRepository) BumpSubtreeVersionsByPath(ctx context.Context, path string) error { if r.redis == nil { return nil } prefixes := splitPathPrefixes(path) pipe := r.redis.Pipeline() for _, prefix := range prefixes { pipe.Incr(ctx, key.KeyDocSubtreeVersion(prefix)) } _, err := pipe.Exec(ctx) if err == redis.Nil { return nil } return err } func getCachedSubtreeIDs(ctx context.Context, key string) ([]int64, bool, error) { var ids []int64 ok, err := cache.GetJSON(ctx, key, &ids) if err != nil { return nil, false, err } if !ok { return nil, false, nil } return ids, true, nil } func setCachedSubtreeIDs(ctx context.Context, key string, ids []int64) { ttl := subtreeCacheTTL if len(ids) == 0 { ttl = subtreeEmptyTTL } if err := cache.SetJSON(ctx, key, ids, ttl); err != nil { logrus.Warnf("set subtree cache failed, key=%s, err=%v", key, err) } } func (r *DocumentRepository) fetchDocumentsByIDs(ids []int64) ([]*model.Document, error) { if len(ids) == 0 { return []*model.Document{}, nil } var docs []*model.Document if err := r.db.Model(&model.Document{}).Where("id IN ?", ids).Find(&docs).Error; err != nil { return nil, err } docMap := make(map[int64]*model.Document, len(docs)) for _, doc := range docs { docMap[doc.ID] = doc } ordered := make([]*model.Document, 0, len(ids)) for _, id := range ids { if doc, ok := docMap[id]; ok { ordered = append(ordered, doc) } } return ordered, nil }