前端显示缓存信息
This commit is contained in:
+83
-10
@@ -41,6 +41,8 @@ type urlKeywordsCache struct {
|
||||
// urlKeywordsEntry LRU 缓存条目
|
||||
type urlKeywordsEntry struct {
|
||||
URL string // URL(用于删除时从 map 中移除)
|
||||
Title string // 页面标题(从 bbolt Snippet 缓存获取)
|
||||
Snippet string // 摘要(从 bbolt Snippet 缓存获取)
|
||||
Keywords []urlKeywordInfo // 关键词列表
|
||||
}
|
||||
|
||||
@@ -60,19 +62,22 @@ func newURLKeywordsCache(maxSize int) *urlKeywordsCache {
|
||||
}
|
||||
|
||||
// Put 写入或更新缓存
|
||||
func (c *urlKeywordsCache) Put(url string, keywords []urlKeywordInfo) {
|
||||
func (c *urlKeywordsCache) Put(url string, title, snippet string, keywords []urlKeywordInfo) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
// 已存在,移到队尾(更新新鲜度)
|
||||
if elem, ok := c.items[url]; ok {
|
||||
c.order.MoveToBack(elem)
|
||||
elem.Value.(*urlKeywordsEntry).Keywords = keywords
|
||||
entry := elem.Value.(*urlKeywordsEntry)
|
||||
entry.Keywords = keywords
|
||||
entry.Title = title
|
||||
entry.Snippet = snippet
|
||||
return
|
||||
}
|
||||
|
||||
// 新增到队尾
|
||||
entry := &urlKeywordsEntry{URL: url, Keywords: keywords}
|
||||
entry := &urlKeywordsEntry{URL: url, Title: title, Snippet: snippet, Keywords: keywords}
|
||||
elem := c.order.PushBack(entry)
|
||||
c.items[url] = elem
|
||||
|
||||
@@ -87,13 +92,14 @@ func (c *urlKeywordsCache) Put(url string, keywords []urlKeywordInfo) {
|
||||
}
|
||||
|
||||
// Get 读取缓存,同时更新新鲜度
|
||||
func (c *urlKeywordsCache) Get(url string) ([]urlKeywordInfo, bool) {
|
||||
func (c *urlKeywordsCache) Get(url string) (*urlKeywordsEntry, bool) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if elem, ok := c.items[url]; ok {
|
||||
c.order.MoveToBack(elem) // 访问过,移到队尾
|
||||
return elem.Value.(*urlKeywordsEntry).Keywords, true
|
||||
entry := elem.Value.(*urlKeywordsEntry)
|
||||
return entry, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
@@ -105,6 +111,17 @@ func (c *urlKeywordsCache) Stats() (size int, maxSize int) {
|
||||
return len(c.items), c.maxSize
|
||||
}
|
||||
|
||||
// ListAll 返回所有缓存条目列表(按访问时间从旧到新)
|
||||
func (c *urlKeywordsCache) ListAll() []*urlKeywordsEntry {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
entries := make([]*urlKeywordsEntry, 0, len(c.items))
|
||||
for elem := c.order.Front(); elem != nil; elem = elem.Next() {
|
||||
entries = append(entries, elem.Value.(*urlKeywordsEntry))
|
||||
}
|
||||
return entries
|
||||
}
|
||||
|
||||
// Server 是搜索 HTTP 服务器,同时内嵌收获服务(统一在同一端口)。
|
||||
type Server struct {
|
||||
db *storage.DB
|
||||
@@ -217,6 +234,7 @@ func (s *Server) Handler() http.Handler {
|
||||
mux.HandleFunc("/admin/crawl/status", s.handleAdminCrawlStatus)
|
||||
mux.HandleFunc("/admin/url/keywords", s.handleUrlKeywords)
|
||||
mux.HandleFunc("/admin/url/keywords/stats", s.handleUrlKeywordsStats)
|
||||
mux.HandleFunc("/admin/url/keywords/list", s.handleUrlKeywordsList)
|
||||
// 静态文件(SPA fallback)
|
||||
mux.Handle("/", spaHandler{dist: "dist"})
|
||||
return mux
|
||||
@@ -821,7 +839,7 @@ func (s *Server) handleAdminCrawlStatus(w http.ResponseWriter, r *http.Request)
|
||||
json.NewEncoder(w).Encode(s.crawler.GetCrawlStatus())
|
||||
}
|
||||
|
||||
// handleUrlKeywords 返回指定 URL 的关键词列表(LRU 缓存查询)
|
||||
// handleUrlKeywords 返回指定 URL 的完整缓存信息(LRU 缓存查询)
|
||||
func (s *Server) handleUrlKeywords(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
@@ -841,17 +859,23 @@ func (s *Server) handleUrlKeywords(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
keywords, ok := s.urlKeywords.Get(url)
|
||||
entry, ok := s.urlKeywords.Get(url)
|
||||
|
||||
resp := struct {
|
||||
URL string `json:"url"`
|
||||
Title string `json:"title"`
|
||||
Snippet string `json:"snippet"`
|
||||
Keywords []urlKeywordInfo `json:"keywords"`
|
||||
Cached bool `json:"cached"`
|
||||
}{
|
||||
URL: url,
|
||||
Keywords: keywords,
|
||||
Cached: ok,
|
||||
}
|
||||
if ok {
|
||||
resp.Title = entry.Title
|
||||
resp.Snippet = entry.Snippet
|
||||
resp.Keywords = entry.Keywords
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(resp)
|
||||
}
|
||||
@@ -884,6 +908,47 @@ func (s *Server) handleUrlKeywordsStats(w http.ResponseWriter, r *http.Request)
|
||||
json.NewEncoder(w).Encode(resp)
|
||||
}
|
||||
|
||||
// handleUrlKeywordsList 返回所有缓存条目(按访问时间从旧到新)
|
||||
func (s *Server) handleUrlKeywordsList(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
|
||||
if r.Method != http.MethodGet && r.Method != http.MethodOptions {
|
||||
http.Error(w, `{"error":"method not allowed"}`, http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
if r.Method == http.MethodOptions {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
|
||||
entries := s.urlKeywords.ListAll()
|
||||
size, maxSize := s.urlKeywords.Stats()
|
||||
|
||||
// 转换为前端需要的格式
|
||||
items := make([]map[string]any, len(entries))
|
||||
for i, e := range entries {
|
||||
items[i] = map[string]any{
|
||||
"url": e.URL,
|
||||
"title": e.Title,
|
||||
"snippet": e.Snippet,
|
||||
"keywords": e.Keywords,
|
||||
}
|
||||
}
|
||||
|
||||
resp := struct {
|
||||
Items []map[string]any `json:"items"` // 缓存条目列表
|
||||
Size int `json:"size"` // 当前缓存的 URL 数量
|
||||
MaxSize int `json:"max_size"` // 缓存容量上限
|
||||
}{
|
||||
Items: items,
|
||||
Size: size,
|
||||
MaxSize: maxSize,
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(resp)
|
||||
}
|
||||
|
||||
// ---- 搜索处理器 ----
|
||||
|
||||
// searchResponse 是搜索 API 的 JSON 响应结构。
|
||||
@@ -1800,7 +1865,7 @@ func (s *Server) handleIngest(w http.ResponseWriter, r *http.Request) {
|
||||
go s.Flush()
|
||||
}
|
||||
|
||||
// 更新 URL→关键词 LRU 缓存
|
||||
// 更新 URL→关键词 LRU 缓存(从 bbolt 获取标题和摘要)
|
||||
keywords := make([]urlKeywordInfo, len(payload.Keywords))
|
||||
for i, kw := range payload.Keywords {
|
||||
keywords[i] = urlKeywordInfo{
|
||||
@@ -1808,7 +1873,15 @@ func (s *Server) handleIngest(w http.ResponseWriter, r *http.Request) {
|
||||
Weight: kw.Weight,
|
||||
}
|
||||
}
|
||||
s.urlKeywords.Put(payload.URL, keywords)
|
||||
title, snippet := "", ""
|
||||
if snippetEntry, err := s.db.GetSnippet(payload.URL); err == nil {
|
||||
title = snippetEntry.Title
|
||||
snippet = snippetEntry.Description
|
||||
if snippet == "" {
|
||||
snippet = snippetEntry.Text
|
||||
}
|
||||
}
|
||||
s.urlKeywords.Put(payload.URL, title, snippet, keywords)
|
||||
|
||||
w.Write([]byte("ok"))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user