ai_xiaopei
6 days ago 1196f409d86bc61e7596eb274840244a62ce84ba
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
package index
 
import (
    "database/sql"
    "encoding/json"
    "sort"
)
// FTSResult 全文搜索结果
type FTSResult struct {
    ID      int64
    Path    string
    Title   string
    Section string
    Rank    float64
}
 
// CreateFTS 创建 FTS5 虚拟表
func (s *Store) CreateFTS() error {
    _, err := s.db.Exec(`
        CREATE VIRTUAL TABLE IF NOT EXISTS nodes_fts USING fts5(
            title,
            content,
            tags,
            entities
        )
    `)
    return err
}
 
// PopulateFTS 填充 FTS 索引
func (s *Store) PopulateFTS() error {
    _, err := s.db.Exec(`
        INSERT INTO nodes_fts(rowid, title, content, tags, entities)
        SELECT id, title, content_fts, tags, entities FROM nodes
    `)
    return err
}
 
// FTSSearch 全文搜索
func (s *Store) FTSSearch(keywords []string, limit int) ([]FTSResult, error) {
    if len(keywords) == 0 {
        return nil, nil
    }
 
    // 对每个关键词单独搜索,然后合并结果(去重)
    // 这样即使某个关键词匹配不到,其他关键词也能找到结果
    seen := make(map[int64]bool)
    var allResults []FTSResult
 
    for _, kw := range keywords {
        rows, err := s.db.Query(`
            SELECT n.id, n.path, n.title, n.section, fts.rank
            FROM nodes_fts fts
            JOIN nodes n ON n.id = fts.rowid
            WHERE nodes_fts MATCH ?
            ORDER BY fts.rank
            LIMIT ?
        `, kw, limit)
        if err != nil {
            // 单个关键词搜索失败,跳过继续
            continue
        }
 
        for rows.Next() {
            var r FTSResult
            if err := rows.Scan(&r.ID, &r.Path, &r.Title, &r.Section, &r.Rank); err != nil {
                rows.Close()
                continue
            }
            // 去重
            if !seen[r.ID] {
                seen[r.ID] = true
                allResults = append(allResults, r)
            }
        }
        rows.Close()
    }
 
    // 按 rank 排序(FTS5 的 rank 越小越好)
    sort.Slice(allResults, func(i, j int) bool {
        return allResults[i].Rank < allResults[j].Rank
    })
 
    // 限制返回数量
    if limit > 0 && len(allResults) > limit {
        allResults = allResults[:limit]
    }
 
    return allResults, nil
}
 
// GetNodeContent 获取节点内容
func (s *Store) GetNodeContent(id int64) (string, []string, []string, error) {
    var content, tagsJSON, entitiesJSON string
    err := s.db.QueryRow("SELECT content_fts, tags, entities FROM nodes WHERE id = ?", id).
        Scan(&content, &tagsJSON, &entitiesJSON)
    if err == sql.ErrNoRows {
        return "", nil, nil, nil
    }
    if err != nil {
        return "", nil, nil, err
    }
 
    // 解析 JSON 数组
    var tags, entities []string
    if tagsJSON != "" {
        if err := json.Unmarshal([]byte(tagsJSON), &tags); err != nil {
            tags = nil
        }
    }
    if entitiesJSON != "" {
        if err := json.Unmarshal([]byte(entitiesJSON), &entities); err != nil {
            entities = nil
        }
    }
 
    return content, tags, entities, nil
}