fix: 搜索模块审计修复 — ATM+NOT、affiliation、分页、SB 等 12 项漏洞
This commit is contained in:
@@ -410,8 +410,8 @@ function restoreFromQuery() {
|
||||
const ps = parseInt(String(route.query.page_size))
|
||||
if (ps >= 10 && ps <= 100) pageSize.value = ps
|
||||
}
|
||||
// keyset 游标不能从 URL 恢复 → page>1 回退到首页
|
||||
if (sort.value === 'date' && restoredPage.value > 1) restoredPage.value = 1
|
||||
// keyset 游标不能从 URL 恢复 → page>1 回退到首页(所有排序模式)
|
||||
if (restoredPage.value > 1) restoredPage.value = 1
|
||||
}
|
||||
|
||||
// 选择预设(1y/5y/10y)时清除自定义年份并自动搜索
|
||||
|
||||
@@ -35,10 +35,13 @@ const searched = ref(false)
|
||||
const searchTotal = ref(0)
|
||||
|
||||
// keyset 游标(用于"加载更多",page 被忽略)
|
||||
const cursorDate = ref<string | null>(null)
|
||||
const cursorVal = ref<string | null>(null)
|
||||
const cursorId = ref<string | null>(null)
|
||||
const hasMoreItems = ref(false)
|
||||
|
||||
// 搜索请求 AbortController,防竞态
|
||||
const searchController = ref<AbortController | null>(null)
|
||||
|
||||
// ── 公开统计 ──
|
||||
const platformStats = ref({ literature_total: 0, journal_total: 0, daily_avg_30d: 0 })
|
||||
const statsLoading = ref(true)
|
||||
@@ -124,7 +127,7 @@ async function loadHomepageFeed() {
|
||||
const items = data.items || []
|
||||
if (items.length > 0) {
|
||||
const last = items[items.length - 1]
|
||||
cursorDate.value = (last.article_date || last.pub_date)?.slice(0, 10) || null
|
||||
cursorVal.value = (last.article_date || last.pub_date)?.slice(0, 10) || null
|
||||
cursorId.value = last.id || null
|
||||
}
|
||||
searched.value = true
|
||||
@@ -133,8 +136,13 @@ async function loadHomepageFeed() {
|
||||
|
||||
// ── 数据加载核心 ──
|
||||
async function fetchData(resetPage = true) {
|
||||
// P2-F14: 取消上次未完成的请求,防竞态
|
||||
searchController.value?.abort()
|
||||
searchController.value = new AbortController()
|
||||
const signal = searchController.value.signal
|
||||
|
||||
if (resetPage) {
|
||||
cursorDate.value = null
|
||||
cursorVal.value = null
|
||||
cursorId.value = null
|
||||
}
|
||||
loading.value = true
|
||||
@@ -148,11 +156,11 @@ async function fetchData(resetPage = true) {
|
||||
if (searchParams.value.retracted) body.retracted = searchParams.value.retracted
|
||||
if (searchParams.value.negative_result) body.negative_result = searchParams.value.negative_result
|
||||
// keyset 游标
|
||||
if (cursorDate.value && cursorId.value) {
|
||||
body.cursor_val = cursorDate.value
|
||||
if (cursorVal.value && cursorId.value) {
|
||||
body.cursor_val = cursorVal.value
|
||||
body.cursor_id = cursorId.value
|
||||
}
|
||||
const { data } = await api.post('/features/search/advanced', body)
|
||||
const { data } = await api.post('/features/search/advanced', body, { signal })
|
||||
if (resetPage) {
|
||||
feedItems.value = data.items || []
|
||||
searchTotal.value = data.total ?? 0
|
||||
@@ -161,17 +169,9 @@ async function fetchData(resetPage = true) {
|
||||
}
|
||||
hasMoreItems.value = data.has_more ?? false
|
||||
// keyset 游标(全部使用服务端返回的游标,通用所有排序模式)
|
||||
if (searchParams.value.sort !== 'date') {
|
||||
// 非 date 排序由服务端返回 cursor_val,用 response 字段
|
||||
if (data.cursor_val) {
|
||||
cursorDate.value = data.cursor_val
|
||||
cursorId.value = data.cursor_id
|
||||
}
|
||||
} else {
|
||||
if (data.cursor_val && data.cursor_id) {
|
||||
cursorDate.value = data.cursor_val
|
||||
cursorId.value = data.cursor_id
|
||||
}
|
||||
if (data.cursor_val) {
|
||||
cursorVal.value = data.cursor_val
|
||||
cursorId.value = data.cursor_id ?? null
|
||||
}
|
||||
searched.value = true
|
||||
} catch (e) { toast.apiError(e, '搜索文献失败,请重试') }
|
||||
@@ -331,21 +331,17 @@ async function loadMore() {
|
||||
if (searchParams.value.date_to) body.date_to = searchParams.value.date_to
|
||||
if (searchParams.value.retracted) body.retracted = searchParams.value.retracted
|
||||
if (searchParams.value.negative_result) body.negative_result = searchParams.value.negative_result
|
||||
// P3-6: precision_mode 不再发送
|
||||
if (cursorDate.value && cursorId.value) {
|
||||
body.cursor_date = cursorDate.value
|
||||
// P1-F4: 发 cursor_val(非 cursor_date),服务端兼容两者
|
||||
if (cursorVal.value && cursorId.value) {
|
||||
body.cursor_val = cursorVal.value
|
||||
body.cursor_id = cursorId.value
|
||||
}
|
||||
const { data } = await api.post('/features/search/advanced', body)
|
||||
feedItems.value.push(...(data.items || []))
|
||||
hasMoreItems.value = data.has_more ?? false
|
||||
// 更新游标
|
||||
const items = data.items || []
|
||||
if (items.length > 0) {
|
||||
const last = items[items.length - 1]
|
||||
cursorDate.value = (last.article_date || last.pub_date)?.slice(0, 10) || null
|
||||
cursorId.value = last.id || null
|
||||
}
|
||||
// P1-F9: 统一使用服务端返回的游标(替代手动从末条提取)
|
||||
cursorVal.value = data.cursor_val ?? null
|
||||
cursorId.value = data.cursor_id ?? null
|
||||
} catch (e) { toast.apiError(e, '加载更多失败,请重试') }
|
||||
finally { loadingMore.value = false }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user