惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
A
About on SuperTechFans
IT之家
IT之家
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Blog — PlanetScale
Blog — PlanetScale
aimingoo的专栏
aimingoo的专栏
云风的 BLOG
云风的 BLOG
The GitHub Blog
The GitHub Blog
Vercel News
Vercel News
G
Google Developers Blog
J
Java Code Geeks
宝玉的分享
宝玉的分享
T
Tailwind CSS Blog
Cloudbric
Cloudbric
L
LINUX DO - 最新话题
MyScale Blog
MyScale Blog
H
Heimdal Security Blog
PCI Perspectives
PCI Perspectives
Attack and Defense Labs
Attack and Defense Labs
S
Security @ Cisco Blogs
Latest news
Latest news
I
Intezer
L
Lohrmann on Cybersecurity
C
CXSECURITY Database RSS Feed - CXSecurity.com
月光博客
月光博客
T
Threatpost
博客园 - 【当耐特】
S
Schneier on Security
P
Privacy International News Feed
G
GRAHAM CLULEY
T
Tenable Blog
AWS News Blog
AWS News Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
雷峰网
雷峰网
博客园 - Franky
Engineering at Meta
Engineering at Meta
美团技术团队
S
Secure Thoughts
T
Troy Hunt's Blog
Microsoft Security Blog
Microsoft Security Blog
SecWiki News
SecWiki News
V
Visual Studio Blog
人人都是产品经理
人人都是产品经理
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Cisco Talos Blog
Cisco Talos Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Martin Fowler
Martin Fowler
Webroot Blog
Webroot Blog
Google DeepMind News
Google DeepMind News
H
Hackread – Cybersecurity News, Data Breaches, AI and More

姓王者的博客

Linux用户Secure Boot自主维护指南 | 姓王者的博客 MAD Bugs 已经开始——关于信息安全的军备竞赛 | 姓王者的博客 解决钉钉Dingtalk无法在Linux新版内核上启动问题-修复可执行栈错误 | 姓王者的博客 突发:GitHub 正遭受大规模 Issue 赌博广告轰炸 | 姓王者的博客 Ubuntu26.04-beta体验:坚毅浣熊! | 姓王者的博客 fakeclaw装作龙虾发贴吧 | 姓王者的博客 找回12年前的QQ记忆 | 姓王者的博客 在Linux上玩Flash网页游戏-洛克王国 | 姓王者的博客 Copilot将使用交互数据来训练 | 姓王者的博客 重要通知-请更新我的GPG公钥 | 姓王者的博客 为了自由Android | 姓王者的博客 GPL"2,3"事 | 姓王者的博客 短文-对VitePlus的一点🤏小贡献 | 姓王者的博客 Bing收录没了?亲测有效的快速恢复指南 | 姓王者的博客 解决桌面设备二维码快速识别的工具-ClipQR | 姓王者的博客 解决 Nautilus 自定义终端插件安装依赖问题 | 姓王者的博客 OpenClaw 该熄火了 | 姓王者的博客 Vite8 - 统一的基建开始 | 姓王者的博客 Astro 6 推出啦 | 姓王者的博客 ubuntu的openvpn异常暂停推送更新 | 姓王者的博客 Ubuntu 24.04 安装 Win10 虚拟机 | 姓王者的博客 ESA-后记:热爱阿里云 | 姓王者的博客 Moonbit 0.8.0 重大发布,我也要改一下我的包 | 姓王者的博客 ESA Pages 边缘开发大赛获奖 | 姓王者的博客 Astro: 优化katex,mermaid和灯箱使用 | 姓王者的博客 从edgeone迁移到esa | 姓王者的博客 出租人类:AI时代的荒诞与真实 | 姓王者的博客 Astro 5.17构建性能优化实践:从18s到13s | 姓王者的博客 Moonbit License Checker 开发使用 | 姓王者的博客 Stalux Astro博客主题自荐 | 姓王者的博客 把Hexo永久链接迁移到Astro | 姓王者的博客 再见👋 LeanCloud | 姓王者的博客 2025年终总结 | 姓王者的博客 许可合规-fancybox | 姓王者的博客 博客主题的软著下来了 | 姓王者的博客 友链图谱 - 汇聚千丝万缕的联系 | 姓王者的博客 chen-er 专为Chen式ER图打造的npm包 | 姓王者的博客 为什么我推荐你使用GPG来加密你的邮件 | 姓王者的博客 2025第三方客户端登录东北大学邮箱 | 姓王者的博客 好久没更新了,过去与未来 | 姓王者的博客 1024 重要的日子 | 姓王者的博客 再也不见Windows10 | 姓王者的博客 偷梁换柱,解决Ubuntu24.04安装Packet Tracer缺失依赖问题 | 姓王者的博客 中秋-来试试Moonbit吧 | 姓王者的博客 Obsidian使用体验 | 姓王者的博客 猪猪侠·一只老猪的逆袭 | 姓王者的博客 国庆日纪念 | 姓王者的博客 GNU 42周年,AI时代的自由精神 | 姓王者的博客 解决Linux上启动游戏总是默认English的情况 | 姓王者的博客 7x24:运维使命 | 姓王者的博客 Tauri2.x实现系统菜单导航Vue路由 | 姓王者的博客 计算机图形学-基本图形生成算法 | 姓王者的博客 数据库原理-关系数据 | 姓王者的博客 数据库原理-设计技巧 | 姓王者的博客 数据库原理E-R模型 | 姓王者的博客 旧忆 - 我曾玩过的游戏 | 姓王者的博客 再谈自由软件 | 姓王者的博客 可能解决Tauri多窗口应用阻塞问题 | 姓王者的博客 Xingwangzhe! Z-Library We miss you and we need your help | 姓王者的博客 计算机组成原理第二章 - 定点数与浮点数 | 姓王者的博客 计算机组成原理第一章 | 姓王者的博客 不小心写死循环窗口弹出了 | 姓王者的博客 美化Grub界面 | 姓王者的博客 计算机图形学-图形的表示与数据结构 | 姓王者的博客 计算机图形学绪论 | 姓王者的博客 为什么说,大学教育与社会脱节 | 姓王者的博客 VSCode Remote 远程连接服务器记录 | 姓王者的博客 解决Tauri2.x拖拽事件问题 | 姓王者的博客 新学期第一课《计算机图形学》报告 | 姓王者的博客 Tauri在GNOME46+上通知无效的临时解决方法 | 姓王者的博客 窃文者:未经授权转载我文章 | 姓王者的博客 GPG公钥分享文化 | 姓王者的博客 解决在ubuntu上,打包vscode插件问题 | 姓王者的博客 伪造squaremap的玩家显示 | 姓王者的博客 爆,沉浸式翻译泄露敏感信息 | 姓王者的博客 读书:《Free as in Freedom》——若为自由故 | 姓王者的博客 首页文章列表懒加载优化 | 姓王者的博客 Ubuntu 24.04 安装 Vivado 2018.3 | 姓王者的博客 腾讯Edgeone免费版体验 | 姓王者的博客 在 Ubuntu 上实现 Thetis FIDO U2F 密钥登录 | 姓王者的博客 Thetis物理密钥,为什么我们应该使用物理密钥 | 姓王者的博客 高考生过来看!教你精准转换录取位次! | 姓王者的博客 ubuntu无法访问windows磁盘问题 | 姓王者的博客 收信有感,防范钓鱼邮件 | 姓王者的博客 自由不止软件-记录一次zlib上传书籍 | 姓王者的博客 时隔两年,通关夺命邮差2 | 姓王者的博客 博客一周年了,竟然坚持了下来 | 姓王者的博客 Minecraft大电影:不建不散! | 姓王者的博客 是时候了解docker了! | 姓王者的博客 编译原理:LL(1)文法 | 姓王者的博客 编译原理:文法转换 | 姓王者的博客 离散数学:子群的陪集及拉格朗日定理 | 姓王者的博客 离散数学:半群,独异点 | 姓王者的博客 《人工智能生成合成内容标识办法》与个人博客--我们应该做什么? | 姓王者的博客 通识学习:形式语言与自动机,布尔代数与数进制 | 姓王者的博客 Webmapview:一个我的世界内置网页地图浏览Fabric模组 | 姓王者的博客 海岛机器人农场试玩 | 姓王者的博客 正则表达式学习 | 姓王者的博客 抓取个人博客文章目录到github主页 | 姓王者的博客 制作github贪吃蛇贡献图 | 姓王者的博客
学习开发Astro博客后台(二)重载之殇 | 姓王者的博客
作者:xingwangzhe · 2025-07-04 · via 姓王者的博客

🕒 阅读时间:4 分钟 📝 字数:1207 👀 阅读量: Loading...

添加md编辑

在前文中,已经实现了非常简陋的功能,下一步准备粗略地写一个md编辑,然后再通过fetch请求将数据发送到后端。

Vditor是一个非常好用的前端Markdown编辑器,它支持实时预览、代码高亮、表格等功能,还内置katex,mermaid等功能,非常适合用来编辑博客文章。于是我使用这个来粗略实现一下编辑器

潦草编辑器

---

const { options = {},post = {} } = Astro.props;

export const prerender = false;

---

<div class="editor-container">

<link rel="stylesheet" href="https://unpkg.com/vditor/dist/index.css" />

<div class="save-status" id="save-status">就绪</div>

<div id="Editor"></div>

<script is:inline define:vars={{options,post}}>

window.editorOptions = options || {};

window.post = post || {};

</script>

<script>

import Vditor from 'vditor'

// Declare editorOptions on window

declare global {

interface Window {

editorOptions: any,

post: any;

}

}

<!--省略-->

let vditor = new Vditor('Editor', {

...window.editorOptions,//自定义的配置

after: () => {

//console.log('Vditor 初始化完成');

//afte钩子,实现初始化内容

if (window.editorOptions.value) {

vditor.setValue(window.editorOptions.value);

}

updateSaveStatus('就绪', 'ready'); // 监听自定义 HMR 事件

if (import.meta.hot) {

import.meta.hot.on('locastrol-content-update', (data) => {

//console.log('[Locastrol] 检测到内容文件更新,但已阻止页面重载:', data);

updateSaveStatus('内容已同步', 'synced');

setTimeout(() => {

updateSaveStatus('就绪', 'ready');

}, 2000);

})

}

},

input: ()=>{

// 清除之前的自动保存定时器

if (autoSaveTimeout) {

clearTimeout(autoSaveTimeout);

}

if (!isAutoSaving) {

updateSaveStatus('正在编辑...', 'editing');

}

// 设置新的自动保存定时器(防抖)

autoSaveTimeout = setTimeout(()=>{

autoSave(vditor.getValue());

}, 1500);

}

});

<!--省略-->

:::warning

特别注意的是,定义<script define:vars><script>会导致import Vditor from 'vditor'失效

因为此时script变成了module类型,阻止了Vditor的正常加载

:::

:::tip

下面是一个技巧,来变相传参 ,这样参数才能正确写入到Vditor中

<script is:inline define:vars={{options,post}}>

window.editorOptions = options || {};

window.post = post || {};

</script>

<script>

import Vditor from 'vditor'

// Declare editorOptions on window

declare global {

interface Window {

editorOptions: any,

post: any;

}

}

...

</script>

:::

:::danger 但同时,vars会裸暴露在前端

所以不要一股脑直接传递一个结构体对象,而是具体精细的传递需要的参数

特别注意不要把私密信息astro构建时传递到前端运行时

:::

阻止后台页面更新

由于我后台使用了astro:content来加载内容,所以每次内容更新都会导致页面重载,这就导致了后台md编辑器无法正常工作

所以需要阻止页面重载,理论上可以通过import.meta.hot来监听astro:content的更新事件,然后阻止页面重载

但是我问了Copilot,并没有很好地实现,反复折磨下,终于给出了相对可行的方案

前台/后台页面刷新控制机制的技术原理

问题的根源

在开发Astro博客后台时,遇到的核心问题是:前台页面需要正常刷新以显示最新内容,而后台编辑页面绝对不能刷新,否则会丢失用户正在编辑的内容。

这个问题的根源在于Astro的HMR(Hot Module Replacement)机制:

  • src/content/posts/目录下的markdown文件发生变化时
  • Astro会触发astro:content模块的更新
  • 默认情况下,这会导致所有使用该模块的页面重新加载

技术实现原理

1. HMR事件拦截机制

理论上这么说,但是我客户端基本没实现,问了ai也没正确实现 :(

if (import.meta.hot) {

import.meta.hot.on("locastrol-content-update", (data) => {

// 拦截自定义更新事件,阻止页面重载

updateSaveStatus("内容已同步", "synced");

});

}

工作原理:

  • import.meta.hot是Vite提供的HMR API
  • 通过监听自定义事件locastrol-content-update来接收内容更新通知
  • 这个事件是由外部工具(如locastrol)发送的,而不是Astro原生的重载事件

2. 页面类型差异化处理

这个是容易实现的,但是只是实现了一半,因为astro:content会导致所有.astro页面刷新

前台页面(正常刷新):

  • 使用标准的astro:content导入
  • 保持Astro默认的HMR行为
  • 内容更新时自动重载页面

后台页面(不能阻止刷新):

  • 即使通过外部API获取内容数据,而不直接使用astro:content,也没法阻止刷新
  • 使用自定义HMR事件监听机制,我没实现
  • 手动控制内容同步,避免页面重载,呃呃,体验更不好了,还得手动…

3.真正的实现?(完全地copilot,我真不会)

采用WebSocket消息拦截 + HMR更新阻止

核心技术架构

1. 客户端状态追踪系统

// 客户端状态管理

const clientStates = new Map<string, ClientState>();

interface ClientState {

isEditorPage: boolean;

url: string;

lastActivity: number;

}

  • 通过x-client-id请求头唯一标识每个客户端
  • 维护全局的客户端状态映射表
  • 实时追踪哪些客户端正在编辑器页面

2. 心跳机制

// 心跳API处理

if (url === "/api/locastrol/heartbeat" && req.method === "POST") {

clientStates.set(clientId, {

isEditorPage: Boolean(isEditorPage),

url: clientUrl,

lastActivity: Date.now(),

});

}

  • 客户端每30秒发送心跳包报告状态
  • 服务器根据心跳更新客户端状态
  • 超过30秒无活动的客户端被视为非活跃

3. WebSocket消息拦截

// 完全劫持WebSocket的send方法

const originalSend = server.ws.send;

server.ws.send = function (payload: any, client?: any) {

// 检查是否有编辑器页面活跃

let hasActiveEditor = false;

for (const [clientId, state] of clientStates) {

if (state.isEditorPage && Date.now() - state.lastActivity < 30000) {

hasActiveEditor = true;

break;

}

}

// 如果有编辑器页面活跃,完全阻止所有WebSocket刷新消息

if (hasActiveEditor && typeof payload === "object" && payload !== null) {

if (

payload.type === "full-reload" ||

payload.type === "update" ||

payload.type === "connected" ||

payload.type === "error"

) {

return; // 完全阻止消息发送

}

}

return originalSend.call(this, payload, client);

};

  • 直接劫持Vite开发服务器的WebSocket发送方法
  • 根据活跃编辑器状态决定是否发送刷新消息
  • 实现了”有编辑器活跃时全局阻止刷新”的策略

4. HMR更新拦截

// HMR更新拦截

handleHotUpdate({ file, server }) {

// 检查是否有编辑器页面活跃

let hasActiveEditor = false;

for (const [clientId, state] of clientStates) {

if (state.isEditorPage && (Date.now() - state.lastActivity < 30000)) {

hasActiveEditor = true;

break;

}

}

if (hasActiveEditor) {

return []; // 阻止HMR更新

} else {

return undefined; // 允许正常更新

}

}

  • 在Vite插件层面拦截热更新
  • 返回空数组阻止更新,返回undefined允许更新
  • 实现了文件级别的更新控制

实际工作流程:

1. 用户打开编辑器页面

2. 客户端开始发送心跳包(每30秒)

3. 服务器标记该客户端为"编辑器活跃"

4. 文件系统发生变化

5. Vite尝试触发HMR更新

6. locastrol检测到活跃编辑器

7. 同时拦截HMR事件和WebSocket消息

8. 编辑器页面保持不刷新,前台页面正常刷新

这种实现方案的核心优势在于从根源上阻止了所有可能导致页面刷新的WebSocket消息和HMR事件,而不是依赖客户端的事件监听,确保了编辑器页面的绝对稳定性。

:::tip 但实际上,前台页面倒是能够正常刷新,但是热重载失效了,算了,勉强先这样吧 :::

下个月又要开始课设了,估计没时间写这个后台插件了,只能暂时搁置咯

已经有好几个搁置了,都推到暑假了…没人催的话,我大概会忘记吧…😀💧