安全加固 + 结构清理:修注入/串写/竞态,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:
Sakurasan
2026-09-21 23:59:09 +08:00
parent c762f06cd7
commit dd2994189a
25 changed files with 539 additions and 1411 deletions
+20 -1
View File
@@ -1,5 +1,23 @@
const base = ''
// 侧栏数据(标签 / 最近更新)每页都会重新挂载请求,这里做 30s 的
// 模块级缓存 + 并发去重;失败不缓存,下一次调用会重新请求。
function cached(fn, ttl = 30000) {
let p = null
let at = 0
return (...args) => {
const now = Date.now()
if (p && now - at < ttl) return p
at = now
const cur = fn(...args)
p = cur
cur.catch(() => {
if (p === cur) p = null
})
return cur
}
}
async function request(path, { method = 'GET', body, auth = false } = {}) {
const headers = { 'Content-Type': 'application/json' }
const res = await fetch(base + path, {
@@ -35,7 +53,8 @@ export const publicApi = {
posts: (params = {}) => request('/api/posts?' + new URLSearchParams(params)),
post: (slug) => request('/api/posts/' + encodeURIComponent(slug)),
archive: () => request('/api/archive'),
tags: () => request('/api/tags')
tags: cached(() => request('/api/tags')),
latest: cached(() => request('/api/posts?size=5'))
}
export const adminApi = {