安全加固 + 结构清理:修注入/串写/竞态,DOMPurify 上线,后端补事务与 handler 测试
后端: - ORDER BY 白名单(sanitizeOrder)堵住 ?order= SQL 注入,补回归测试 - 登录限速(每 IP 10 次失败/10 分钟 429)、TLS/反代下 Secure cookie、NewAPI 构造器 - Delete/setTags/MergeTags/DeleteTag 包事务;Archive 去 500 篇上限 - 列表接口裁剪:不传 content_md,长文 content_html 截 600,新增 content_len;health 探 DB 前端: - EditorView 路由复用串写修复(RouterView :key + sync watch 回写原文章) - v-html 出口统一过 DOMPurify(sanitizeHtml),stripTags 改 DOMParser - 列表竞态防护(Home/Tag/Posts 请求序号)、TagView 分页修复 - 侧栏接口 30s 缓存去重;one:unauthorized 监听器泄漏修复 - 删 styles.css 498 行重复块;移除 tailwind/marked/vue-tsc 死依赖;CommandPalette a11y 语义
This commit is contained in:
@@ -31,7 +31,11 @@ const orderMap = {
|
||||
longest: 'reading_minutes DESC'
|
||||
}
|
||||
|
||||
// 快速切换筛选/翻页时只采纳最新一次请求的结果(序号防竞态覆盖)
|
||||
let seq = 0
|
||||
|
||||
async function load() {
|
||||
const my = ++seq
|
||||
loading.value = true
|
||||
error.value = ''
|
||||
try {
|
||||
@@ -41,13 +45,15 @@ async function load() {
|
||||
if (tagFilter.value) params.tag = tagFilter.value
|
||||
if (q.value.trim()) params.q = q.value.trim()
|
||||
const data = await adminApi.posts(params)
|
||||
if (my !== seq) return
|
||||
items.value = data.items || []
|
||||
total.value = data.total || 0
|
||||
selected.value = new Set()
|
||||
} catch (e) {
|
||||
if (my !== seq) return
|
||||
error.value = e.message || '加载失败'
|
||||
} finally {
|
||||
loading.value = false
|
||||
if (my === seq) loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,7 +270,7 @@ function clearFilters() {
|
||||
</div>
|
||||
<div class="stats" aria-hidden="true">
|
||||
<div>{{ p.reading_minutes || 1 }}′</div>
|
||||
<div>{{ (p.content_md || '').length }} 字</div>
|
||||
<div>{{ p.content_len || 0 }} 字</div>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<a href="#" @click.prevent.stop="edit(p.id)">编辑</a>
|
||||
|
||||
Reference in New Issue
Block a user