fix: 第8轮搜索深度审计修复 — 缓存失效、Redis重试、中文标签、翻页稳定性等12项
CRITICAL:
- invalidate_search_cache 清理 atm:* 缓存(MeSH ATM扩展不再用过期结果)
- Pro 方案 api_quota_per_day 1000→10000(修复低于 Free 的数据错误)
- CacheService/RateLimitMiddleware Redis 连接失败60秒自动重试(原永久降级)
- 普通搜索中文输入自动匹配 GlobalTag.name_zh(如"肺癌"通过MeSH标签关联文献)
- AdvancedPubSearchView resolveQuery 添加 seen Set 检测交替 #N 循环引用
HIGH:
- 限速器 _burst_windows 每500请求清理过期条目(防止内存泄漏)
- cron daily_ftp_update 末尾调用 invalidate_search_cache()(自动管道不再用过期缓存)
MEDIUM:
- _apply_order_by ASC 排序加 id tiebreaker(title/journal/first_author翻页跳行/重复)
- _keyset_condition 所有 is_(None) 加 id tiebreaker + __NULL__ 哨兵值
- _field_condition("all") 默认tsvector路径加 journal/journal_iso ILIKE 兜底
- SearchView restoreFromQuery date_preset/year_from/year_to 优先顺序修复
docs: 更新 12/13 搜索文档,移除 CLAUDE.md 陈旧 SQLite 提及
This commit is contained in:
@@ -19,6 +19,7 @@ const router = useRouter(); const route = useRoute()
|
||||
const toast = useToast()
|
||||
const auth = useAuthStore()
|
||||
const displaySettings = useDisplaySettings()
|
||||
const KEYSET_SORTS = new Set(['date', 'cited', 'title', 'journal', 'first_author'])
|
||||
|
||||
// ── 搜索参数 ──
|
||||
const query = ref('')
|
||||
@@ -276,6 +277,7 @@ const { page, total, goToPage } = usePagination({
|
||||
}
|
||||
const body: SearchRequestBody = {
|
||||
query: query.value, page: p, page_size: pageSize.value, sort: sort.value,
|
||||
boolean: 'and',
|
||||
}
|
||||
if (field.value !== 'all') body.field = field.value
|
||||
// P3-6: precision_mode 不再发送(后端已忽略)
|
||||
@@ -339,6 +341,7 @@ const { page, total, goToPage } = usePagination({
|
||||
if (retracted.value) body.retracted = retracted.value
|
||||
if (negativeResult.value) body.negative_result = negativeResult.value
|
||||
const { data } = await api.post('/features/search/advanced', body, { signal })
|
||||
if (gen !== searchGeneration.value) return // 丢弃老旧请求
|
||||
results.value = data.items || []
|
||||
// 游标分页:首页走 COUNT 存 total,后续页沿用;cursor 和数据一起返回
|
||||
if (p === 1) total.value = data.total || 0
|
||||
@@ -370,12 +373,14 @@ const { page, total, goToPage } = usePagination({
|
||||
/** 从路由 query 恢复搜索参数 */
|
||||
function restoreFromQuery() {
|
||||
if (route.query.q) query.value = String(route.query.q)
|
||||
if (route.query.field) field.value = String(route.query.field)
|
||||
if (route.query.sort) sort.value = String(route.query.sort)
|
||||
if (route.query.year_from) yearFromStr.value = String(route.query.year_from)
|
||||
if (route.query.year_to) yearToStr.value = String(route.query.year_to)
|
||||
const VALID_FIELDS = new Set(['all', 'title', 'abstract', 'author', 'affiliation', 'journal'])
|
||||
const VALID_SORTS = new Set(['date', 'cited', 'best_match', 'relevance', 'first_author', 'journal', 'title'])
|
||||
if (route.query.field && VALID_FIELDS.has(String(route.query.field))) field.value = String(route.query.field)
|
||||
if (route.query.sort && VALID_SORTS.has(String(route.query.sort))) sort.value = String(route.query.sort)
|
||||
if (route.query.date_preset && ['1y','5y','10y','custom'].includes(String(route.query.date_preset))) {
|
||||
datePreset.value = String(route.query.date_preset)
|
||||
yearFromStr.value = ''
|
||||
yearToStr.value = ''
|
||||
} else if (route.query.date_from || route.query.date_to) {
|
||||
datePreset.value = null
|
||||
if (route.query.date_from) {
|
||||
@@ -388,6 +393,9 @@ function restoreFromQuery() {
|
||||
urlDateTo.value = dt
|
||||
if (dt.length >= 4) yearToStr.value = dt.slice(0, 4)
|
||||
}
|
||||
} else {
|
||||
if (route.query.year_from) yearFromStr.value = String(route.query.year_from)
|
||||
if (route.query.year_to) yearToStr.value = String(route.query.year_to)
|
||||
}
|
||||
if (route.query.tag) selectedTags.value = String(route.query.tag).split(',')
|
||||
if (route.query.tier) selectedTiers.value = String(route.query.tier).split(',')
|
||||
@@ -410,8 +418,8 @@ function restoreFromQuery() {
|
||||
const ps = parseInt(String(route.query.page_size))
|
||||
if (ps >= 10 && ps <= 100) pageSize.value = ps
|
||||
}
|
||||
// keyset 游标不能从 URL 恢复 → page>1 回退到首页(所有排序模式)
|
||||
if (restoredPage.value > 1) restoredPage.value = 1
|
||||
// keyset 游标不能从 URL 恢复 → page>1 回退首页(仅键集排序模式)
|
||||
if (KEYSET_SORTS.has(sort.value) && restoredPage.value > 1) restoredPage.value = 1
|
||||
}
|
||||
|
||||
// 选择预设(1y/5y/10y)时清除自定义年份并自动搜索
|
||||
@@ -507,8 +515,8 @@ function syncSearchToUrl() {
|
||||
if (medlineOnly.value) q.medline_only = 'true'
|
||||
if (excludePreprints.value) q.exclude_preprints = 'true'
|
||||
if (pageSize.value !== 20) q.page_size = String(pageSize.value)
|
||||
// 页码持久化到 URL(不同排序下使用各自的分页)
|
||||
const currentPage = sort.value === 'date' ? keysetPage.value : page.value
|
||||
// 页码持久化到 URL(Keyset 排序使用 keyset 页码,offset 排序使用 offset 页码)
|
||||
const currentPage = KEYSET_SORTS.has(sort.value) ? keysetPage.value : page.value
|
||||
if (currentPage > 1) q.p = String(currentPage)
|
||||
router.replace({ query: q }).catch(() => {})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user