fix: 搜索模块审计修复 — ATM+NOT、affiliation、分页、SB 等 12 项漏洞
CI / backend (push) Canceled after 0s
CI / frontend (push) Canceled after 0s

This commit is contained in:
34047007@qq.com
2026-07-27 16:57:13 +08:00
parent 4a24f764f7
commit 4f6024aa7a
6 changed files with 93 additions and 61 deletions
+2 -2
View File
@@ -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)时清除自定义年份并自动搜索
+23 -27
View File
@@ -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 }
}