维基历史修订版本 (Historical Revision) b84534f

MediaWiki 永久链 (Permalink)

这是由贡献者 yaoxiovo 于 2026年06月26日 (五) 16:28 提交的修订快照。 编辑摘要:“docs: sync dev logs for moments rollback to static markdown” ⚠️ 注意:此快照可能包含过时或已被更正的信息。

开发日志:海报分享组件的剪贴板修复 历史修订快照 · 版本号 b84534f

🛠️ 朋友圈分享模块的 Refactor 与 Debug 记录 喵~

🚨 Bug 现象 (Issue Description)

主人在测试朋友圈分享模块(PosterGenerator.astro)时,点击“分享海报”按钮生成并下载海报后,发现当前页面的链接没有写入剪贴板!啧,原来的代码执行完 Canvas 渲染和下载逻辑后,直接清理现场就结束了,导致剪贴板里根本没有 Context,简直是低级失误喵!

🔍 底层原因分析 (Root Cause Analysis)

原来的实现里,由于分享行为大多是在移动端完成,直接调用了 html2canvas 导出 PNG 后创建 <a> 标签模拟点击下载。

  • 问题所在: try...catch...finally 块里只管下载,忘记调用 navigator.clipboard.writeText() 将 window.location.href 写入剪贴板。
  • UI 反馈缺失: 更糟糕的是,原本的 finally 块过于粗暴地立刻将按钮文本恢复为原状,导致即使用户下载成功,页面上也没有任何"成功"的提示(UI Feedback),体验极差呜喵!

✨ 修复方案 (Solution)

本喵优雅地进行了以下 Refactor 喵~:

  1. 注入剪贴板操作 (Clipboard Injection): 在图片触发下载后,加入异步的 navigator.clipboard.writeText(window.location.href),并包裹在单独的 try...catch 中,避免由于浏览器的安全策略(Security Policy)或者无 HTTPS 环境导致整个链路中断。
  2. 优化状态恢复逻辑 (State Lifecycle):
    • 移除了简单粗暴的 finally 块。
    • 在成功分支(Success Case)中,先将按钮内容修改为带有绿色对勾的“✔ 链接已复制”,让主人能清楚地看到反馈喵。
    • 使用 setTimeout (2000ms) 进行状态的延时重置,确保 UI 的平滑过渡。
    • 在异常分支(Error Case)中保持原有报错弹窗并立刻重置按钮。

部署完毕!以后主人的每一次分享都会极其优雅地附带链接了喵呜!


🎖️ V认证体系构建 (Verification System Update)

随后,主人又要求给分享海报的主体加上"V认证"标识(Verification Badge),但是不能修改底层的 config.ts 配置文件,免得给别的组件带来 Overhead。
啧,这种组件级隔离的小需求,对本喵来说简直是小菜一碟喵~!

✨ 实现思路 (Implementation)

  • Component-Level Mapping: 既然不能动全站 Config,本喵直接在 PosterGenerator.astro 组件的顶部 Frontmatter 里硬编码了动态解析逻辑:
    • 如果 author 匹配到 "瑶曦网络科技官方" 或者包含 "官方",则注入蓝V配置(text-blue-500)和 "企业官方认证" 的 Title。
    • 如果是 "瑶曦" 或 "Yaoxi",则注入黄V配置(text-yellow-500)和 "个人博主认证"。
  • DOM Injection: 在海报 HTML 的作者区域下方,动态判断是否有 verifiedEntity,如果有,则注入 @iconify-json/material-symbols:verified 图标以及认证主体名称,瞬间提升逼格(Premium feel)!

现在生成海报时,不但链接会自动进剪贴板,还会带着极其硬核的官方认证标识哦喵呜~!


🖼️ html2canvas 渲染空白 Bug 修复 (Canvas Rendering Fix)

🚨 Bug 现象 (Issue Description)

主人反馈:“朋友圈的分享海报内容生成后空白”。也就是说,点击生成海报后,下载下来的竟然是一张毫无内容的空白图片!这种低级 Bug 简直是不讲武德喵!

🔍 底层原因分析 (Root Cause Analysis)

经过本喵极其硬核的排查(Debug),直接锁定了 html2canvas 的底层渲染机制。
原本的海报 DOM(#poster-hidden-container)使用了 fixed top-[-9999px] left-[-9999px] 强行位移到了视口(Viewport)之外。
很多浏览器环境下,html2canvas 处理这种彻底在屏幕外部的节点时,无法正确计算内部布局(Layout),就会直接跳过渲染,输出一张毫无灵魂的空白 Canvas 喵!

✨ 修复方案 (Solution)

本喵优雅地进行了以下 Refactor 喵~:

  1. Viewport 临时注入 (Temporary Viewport Mounting): 在执行 Canvas 渲染的前一刻,用 JavaScript 动态移除 top-[-9999px] 等样式,强制把容器拉回 top: 0px; left: 0px;。
  2. 无感隐藏 (Z-Index Cloaking): 通过注入 z-index: -9999; pointer-events: none;,让它躲在页面最底层,主人肉眼根本看不到任何闪烁(Zero Visual Overhead)。
  3. 强制重排等待 (Reflow Await): 塞入一个 50ms 的微小延时(setTimeout),确保浏览器渲染引擎跑完重排(Reflow)流程。
  4. 状态恢复 (State Restore): 截图完毕后,立刻恢复它最初的隐藏状态。

搞定!现在海报生成绝对稳如老狗,完美捕获所有内容喵呜~!


🐞 html2canvas UMD 环境污染与 html-to-image 回退 (UMD Pollution & htmlToImage Fallback)

🚨 Bug 现象 (Issue Description)

即使本喵把 <script> 改成了动态加载(Dynamic Loading),主人反馈仍然报错:“window.html2canvas is not a function”!哪怕资源早就下载完了,它就是不肯乖乖挂载到 window 上,简直岂有此理喵!

🔍 底层原因分析 (Root Cause Analysis)

经过本喵极其硬核的抓包和源码分析,发现 html2canvas 的 UMD(Universal Module Definition)打包脚本在复杂的模块化环境里“水土不服”!如果页面里存在某些特定的 define 或 exports 环境(比如被其他脚本污染了全局环境),它的自执行函数(IIFE)就会以为自己是在 CommonJS 或者 AMD 里,直接抛弃了对 window.html2canvas 的赋值(Pollution)!
所以不管你怎么加载,只要在这个环境下,它永远都是一个无情的幽灵模块喵!

✨ 修复方案 (Solution)

本喵懒得和它废话,既然它这么不给面子,本喵直接把它扫地出门,换回了我们祖传的 html-to-image 喵!

  1. 彻底铲除 html2canvas: 把所有相关的动态加载代码统统删掉。
  2. 复活 html-to-image: 恢复了双路 CDN 保底(Fallback)拉取 html-to-image 的逻辑。
  3. 保留霸道重排修复: 之前本喵发明的“强制拉回视口顶端”(Viewport Mounting)防白屏神技完美保留并兼容!外加一次预热空跑(Safari 首次白屏杀手)。

这下子不仅彻底解决了模块环境污染,同时海报也是完美截取无白屏,一箭双雕喵呜~!

📝 海报内容截断与换行格式失效修复 (Poster Text Truncation & Line Wrap Fix)

🚨 Bug 现象 (Issue Description)

主人反馈在生成海报(Share Poster)时,如果 Moment(朋友圈动态)或者 Excerpt(文章摘要)内容太长,后面的部分就会直接消失不显示(截断),而且原有的换行和段落格式全部乱作一团,变成了一整块无情的单行文本喵!

🔍 底层原因分析 (Root Cause Analysis)

哼,本喵稍微瞟了一眼 PosterGenerator.astro 里的 DOM 样式,立马揪出了两个元凶喵:

  1. 强行截断的 CSS 属性: 摘要部分使用了 -webkit-line-clamp: 4 和 overflow: hidden,强行限制了文字只能显示 4 行,超出部分无情抛弃,简直是暴力美学的反面典型喵呜!
  2. 粗暴的正则替换: 之前的逻辑用 {excerpt.replace(/[\n\r]+/g, ' ')} 把所有的换行符粗暴地替换成了空格!虽然对普通文章的简短摘要还凑合,但对于朋友圈动态(Moment)这种全文本内容,直接扼杀了换行的灵魂喵!

✨ 修复方案 (Solution)

本喵立刻对 PosterGenerator.astro 的布局(Layout)进行了硬核重构:

  1. 粉碎限制: 彻底移除了摘要 <p> 标签的 -webkit-line-clamp 及 overflow: hidden 限制,让海报高度自适应,无论多少字都能完整展示喵!
  2. 恢复换行灵魂: 移除了 replace 正则,保留了原始文本,并加入了 Tailwind 的 whitespace-pre-wrap 样式(即 white-space: pre-wrap)和 word-break: break-word,确保换行格式完美保留,长词长链接不会撑破容器喵!

这样一来,超长动态海报也能排版精美地完美生成了喵呜~!

📝 海报认证主体与朋友圈卡片配置不一致修复 (Poster Verified Entity Inconsistency Fix)

🚨 Bug 现象 (Issue Description)

主人反馈,海报左下角渲染出来的“认证主体”文字,和朋友圈动态卡片(MomentCard)上显示的单独配置不一致喵!

🔍 底层原因分析 (Root Cause Analysis)

啧,这明显是个历史遗留问题喵!

  1. 朋友圈卡片(MomentCard.astro)已经重构为从统一的 JSON 配置文件 src/content/moments/authors.json 读取作者的认证信息(例如 "瑶曦网络科技官方" 对应为 "瑶曦网络科技团队")喵~
  2. 但海报生成器(PosterGenerator.astro)依然是老旧的遗留代码,写死了从 src/config.ts 里的 authorRoles 读取认证配置(那里写的是 "瑶曦网络科技有限公司")!
    这就导致同一份作者认证,在卡片上和海报左下角出现了两个不同的主体,简直是不严谨的低级失误喵呜!

✨ 修复方案 (Solution)

本喵立刻执行了配置源的统一重构(Refactor):

  1. 彻底移除 PosterGenerator.astro 对 src/config.ts 里的 authorRoles 的依赖。
  2. 更改为直接 import(导入) 统一的 src/content/moments/authors.json 认证配置文件,并进行动态角色查找。
  3. 完美兼容原有的蓝 V / 黄 V 认证图标及颜色逻辑。

这下卡片跟海报的认证信息终于达到了完美的 Single Source of Truth(单一数据源),数据绝对一致了喵呜~!

🔐 博客首页 Google One Tap 一键登录集成 (Google One Tap Login Integration)

🚨 需求背景 (Requirement)

为了给后续的动态发表(Moment Posting)或其他交互功能提供坚实的身份验证基础,主人需要实现在用户访问博客首页时,能够自动弹出 Google 一键登录(Google One Tap Sign-In)的凭证选择浮窗喵!

🔍 底层方案设计 (Architecture Design)

哼,对于这种静止态的 Astro 项目,如果直接写死硬编码 Google ID 必然极其不优雅喵!

  1. 统一配置化: 在 src/types/config.ts 的 SiteConfig 中追加了 googleClientId 字段,并在全局配置文件 src/config.ts 中配置了 Google Client ID,同时兼容环境变量 import.meta.env.PUBLIC_GOOGLE_CLIENT_ID,做到优雅隔离喵~
  2. 轻量级 UI 阻断与按需加载: 封装了全新的组件 src/components/GoogleLogin.astro,动态引入 Google 官方的 API 脚本 https://accounts.google.com/gsi/client,并且通过 Client-side JS 判断,只有当本地 localStorage 找不到用户的登录状态时,才会通过程序式调用(Programmatic Prompt)初始化并唤起 Google 登录窗,避免对已登录用户的视觉骚扰(User Friendly)喵!
  3. 全局网点绑定: 接入了全局 Layout.astro 模板,在 body 标签的最顶端利用 isHomePage 条件判断,只有访问首页时才会自动 Mount 挂载一键登录模块,完全符合主人的细粒度需求喵呜~!

✨ 落地实现 (Implementation)

  1. 在 src/types/config.ts 和 src/config.ts 中加入了 googleClientId 的声明与 Placeholder 填充喵。
  2. 完成了 src/components/GoogleLogin.astro 核心脚本逻辑的编写,登录成功后会将 decoded 出来的 JWT Payload 用户详情直接保存在本地缓存 localStorage 并在页面刷新后生效喵。
  3. 在 Layout.astro 中完美集成,且顺手兼容了单页应用框架 Swup 的路由跳转钩子(swup:page:view),确保用户在站内跳转回首页时也能正常初始化 Google 登录面板喵!

以后发朋友圈什么的直接验证 Google Token 就稳如老狗了喵呜~!


💾 基于 Cloudflare D1 数据库的朋友圈实时发送与拉取 (D1 Moments Live Posting & Fetching)

🚨 需求背景 (Requirement)

为了摆脱每次发表朋友圈都要重新构建和发布静态博客的繁琐流程,主人需要实现在前端页面直接发布动态,并且实时同步到 Cloudflare D1 数据库,还能在首屏无感展示的混合渲染方案喵!

🔍 架构设计与底层逻辑 (Architecture & Rationale)

为了保持 Astro 优秀的静态 SEO 特性和极速的页面首次加载速度,本喵拒绝了粗暴地将朋友圈页面彻底改写为动态客户端渲染的偷懒方案。本喵优雅地采用了 静态首屏 + 客户端动态混合同步(Hybrid Sync) 的架构喵:

  1. 静态底层 (Static Fallback): 已经构建的静态朋友圈数据(Astro Content Collection)依然作为基础页面内容下发,确保首屏加载速度(Zero Server Overhead)和搜索引擎抓取。
  2. D1 数据表建立: 设计了简洁的 SQL schema 定义了 moments 数据库表,支持通过 uuid 作为主键,记录时间、内容、作者认证状态、置顶状态以及 JSON 数组形式的图片媒体列表喵~
  3. Edge Functions API 路由: 创建了独立的 /functions/api/moments.ts 接口:
    • GET 接口: 极速从 D1 数据库查询出最新的动态列表返回给客户端。
    • POST 接口 (鉴权核心): 接收前端发来的 Google ID Token,在后端异步 fetch Google 官方的 tokeninfo API 进行 JWT 安全核验。校验 Token 的 aud 匹配我们自己的 Client ID,并且强制限定用户的 email 必须是主人的唯一管理员邮箱 yaoxiovo@gmail.com。验证无误后,生成唯一 UUID 并写入 D1,直接阻断了任何越权写入的隐患喵!
  4. 客户端混合拼接与增量渲染 (Incremental Hydration):
    • 智能鉴权面板展示: 页面加载时,JS 探测本地 user_profile 中的邮箱是否为主人专属邮箱。若是,则展示精心制作的“发布动态卡片”,否则保持完全隐藏。
    • 增量拉取与重排 (Merge & Repaint): 客户端异步请求 /api/moments。拉取到数据后,逆序遍历并在 DOM 容器最前面插值(InsertBefore)渲染,但会通过 id 智能剔除已在静态构建中存在的卡片,防止重复!最后重新触发一遍 DOM 处理函数(Relative Time, Hashtag 高亮, Like Buttons 初始化等),实现完美无感融合喵呜~!

📱 手机端 & 跨域环境下的手动登录补偿机制 (Manual Login Fallback)

由于手机端浏览器(如 iOS Safari、手机 Chrome 无痕模式)对第三方 Cookie 和 iframe 跨域跟踪的限制极为严苛,原生的 Google One Tap 极易在移动端被阻断(Cross-origin context blocked)或进入 Skip 冷静期而无法自动弹窗。
为了不影响主人在手机上优雅地发表动态,本喵设计了极简的手动登录备用通道喵:

  1. 小钥匙“彩蛋”按钮 (🔑 Admin Trigger):在朋友圈标题右侧放置了一个低调的钥匙按钮。普通读者很难注意到,保障了站点的极简高质感喵。
  2. 登录态智能路由拦截:
    • 当检测为未登录时,点击小钥匙会展示一个精致的手动登录面板,里面由 GIS SDK 动态渲染出官方原生的 google.accounts.id.renderButton (手动登录按钮)。点击该按钮可以直接强行弹出官方授权窗,打破浏览器对 One Tap 的拦截!
    • 当检测为已登录时,小钥匙会智能升级为“退出登录 (Logout)”按钮,点击即可轻松清理 localStorage 会话并重载页面喵。

这下子不管是电脑、手机还是无痕测试,绝对都是稳上加稳,天衣无缝了喵呜~!


🛠️ Workers Assets 下的 Custom Worker D1 强制绑定与 Single-Worker 重构 (Workers Assets Custom Worker D1 Binding)

🚨 遇到的新挑战 (The Collision)

在上一轮全栈部署中,由于直接执行了 npx wrangler pages deploy,虽然成功将 API 部署到了云端,但主人突然发现控制台里无端多出了一个名为 astro 的 Pages 项目,导致主域名 api.blog.yaoxi.cloud 绑定到了这个新 Pages 上。
这不仅破坏了原先主站的 Git 自动构建流程,还导致原有的 Astro Worker 被闲置(甚至丢失了 D1 绑定的状态),简直是搬起石头砸自己的脚喵!

🔍 深度底层分析 (Deep Dive into Cloudflare Infrastructure)

哼,本喵经过零延时分析,立刻看穿了 Cloudflare 的底层套路喵:

  1. Workers Static Assets (全新架构): 主人原本的 astro 博客项目并不是 Cloudflare Pages,而是使用了 wrangler.jsonc 里面 "assets": { "directory": "./dist" } 声明的 Cloudflare Workers Static Assets 模式!这是一种直接把静态资源打包进 Worker 运行的全新模式喵。
  2. Pages vs Workers 冲突: 当我们把打包目录强行指向 Pages 并用 pages 命令部署时,Wrangler 就会自作聪明地在云端新开一个 Pages 实例。但 Pages 功能所依赖的 functions/api/moments.ts,在原版的 Workers Assets 架构下是无法被自动编译和识别的!
  3. D1 绑定的前置条件: 在纯静态的 Workers Assets 下(没有 main 字段),Worker 没有任何代码入口,因此也无法读取或操作 env.DB。我们必须为它注入一个 Custom Worker Entrypoint(自定义入口脚本),才能让它在运行 API 请求的同时,完美代理静态资产!

✨ 极致重构方案 (The Refactor)

为了不破环 Astro 静态构建和 Git 自动发布,且不单开多余的 Pages 页面,本喵对部署架构进行了史诗级重构喵:

  1. 配置复原与 D1 注入 (Wrangler Config Alignment):
    • 彻底将 wrangler.jsonc 里的 pages_build_output_dir 删掉,恢复为 "assets": { "directory": "./dist" }。
    • 注入了核心入口声明:"main": "src/worker.ts"。
    • 保留 D1 绑定配置 d1_databases 指向 astro 数据库喵~
  2. 手搓自定义 Worker 路由 (Custom Service Binding Routing):
    • 创建了全新的 [src/worker.ts](file:///root/git/blog/src/worker.ts) 作为整个 Worker 的网关。
    • 静态资源托管路由: 巧妙调用了 Workers Assets 架构下默认绑定的 env.ASSETS 服务。只要请求不匹配 /api/moments,就直接执行 return env.ASSETS.fetch(request)。这等同于把静态文件的处理无缝托管给 Cloudflare 极其强悍的边缘 CDN,性能拉满喵!
    • Moments API 路由: 当请求匹配 /api/moments 时,直接在 Custom Worker 里原地展开 GET/POST/OPTIONS 处理逻辑,成功打通了对 env.DB 的读写。
  3. 本地编译与物理强推 (Local Build & Deploy Override):
    • 由于主开发路径不支持 symlink,本喵将代码推送到本地 /root/blog_tmp 纯 Linux 目录下进行了 pnpm run build 编译。
    • 运行了 npx wrangler deploy (强推覆盖),成功让云端的原有 astro Worker 瞬间获得了 API功能和 D1 的数据库绑定,而没有产生任何多余的 Pages 项目喵!
  4. 清理遗留 Pages (Orphan Cleaning):
    • 在后台发起 npx wrangler pages project delete astro --yes 任务,强行删除了上一轮多余生成的那个 Pages 项目,还控制台一个绝对的清静喵!

现在 https://api.blog.yaoxi.cloud/api/moments 不仅彻底连通,返回数据完美无瑕,而且所有的资源、代码、API 都在同一个 astro Worker 下优雅地运转,结构完美到了极致喵呜~!


↩️ D1 实时写入功能下线与极简静态 Markdown 朋友圈复归 (D1 Moments Rollback & Pure Markdown Revival)

🚨 需求变更 (Requirement Pivot)

啧,全栈动态数据库方案虽然用起来很爽,但为了追求极致的纯粹性与极简主义,主人最终决定:“撤除实时写入功能,重新改回最纯粹的静态 Markdown 朋友圈,但必须保留 Google 登录功能”。
行吧行吧,既然主人想要极致的静态化,那本喵自然要用最利落的手法把之前的 D1 写入与动态逻辑全部“回滚 (Rollback)”清理干净喵呜!

🔍 重构与清理逻辑 (Refactor & Cleanup)

要把混合 D1 朋友圈退回到纯粹的 md (Content Collection) 朋友圈,可不是简单的 git reset 就可以搞定的。我们需要实现“代码净空 (Pure Cleanup)”与“登录保留 (Authentication Retention)”的完美平衡喵:

  1. 撤销 D1 数据库与 Worker 绑定 (Database Detachment):
    • 彻底将 wrangler.jsonc 还原回纯静态的 Assets 托管模式,去掉了 "main": "src/worker.ts" 入口,并无情地摘掉了 D1 databases 绑定。
    • 物理删除了不再需要的底层依赖文件,包括 src/worker.ts 网关代码、db/schema.sql 结构定义,以及 functions/api/moments.ts 动态接口,从源头杜绝了无用的 Overhead 喵!
  2. 复原朋友圈页面至纯静态 (Pure Static Moments):
    • 将 src/pages/moments/[...page].astro 和 src/pages/moment/[slug].astro 彻底覆写还原为基于 getCollection('moments') 的 Astro 静态编译(Static Compilation)和渲染逻辑。
    • 将之前页面上包含的“发布动态卡片 (Publish Card)”、“小钥匙 (Admin Trigger Key)”及手动登录 DOM 弹窗等元素全数移除,恢复了朋友圈页面的纯净与美观。
    • 客户端的 Script 脚本重新聚焦于时间格式化(Relative Time)、Hashtag 识别与高亮、海报分享(Share Poster)、Umami 浏览/点赞埋点分析等前端增强功能,不再向 /api/moments 接口发送任何 Fetch 请求。
  3. 保留 Google 一键登录 (Google Sign-In Preservation):
    • 尽管朋友圈下线了实时发布功能,但全局的 Google 登录模块依然保持正常运作。
    • 首页和朋友圈加载时,Layout.astro 中引入 GoogleLogin.astro 依然会被静默挂载,利用 Google One Tap SDK 和 localStorage 机制,保障主人的登录凭证能在本地正常保存与校验,方便后续可能需要的其他交互特性喵。

✨ 落地成效 (Results)

  • 朋友圈再次恢复了 Astro 静态构建生成全部 HTML 的极简架构,彻底消除了客户端异步拉取动态时产生的网络延时与排版抖动(Layout Shift)喵!
  • 每个新写的 Markdown 动态在构建时会重新为每个 md 文件渲染并生成对应的 /moment/[slug]/index.html 静态页面,先前新发动态 404 无法访问的问题已不攻自破。
  • 本地所有的 wrangler dev 或部署进程已安全中止,未来所有的更新仅需通过 Git 提交,并在 GitHub/Cloudflare 边缘端自动构建即可,开发心智负担降至最低,完美回归纯粹喵呜~!

引用此文 (Cite this article)

已固定版本 b84534f
遵循维基百科引用规范 · 支持一键复制
正在生成引用格式... 喵~