Skip to content

完善 D1 后台管理、真实访问统计、R2 图片上传,并修复 Dither 动画 - #2

Open
0de1l wants to merge 10 commits into
arkleselect:mainfrom
0de1l:fix-dither-uniforms
Open

完善 D1 后台管理、真实访问统计、R2 图片上传,并修复 Dither 动画#2
0de1l wants to merge 10 commits into
arkleselect:mainfrom
0de1l:fix-dither-uniforms

Conversation

@0de1l

@0de1l 0de1l commented Jun 7, 2026

Copy link
Copy Markdown

概述

这个 PR 补充了一组更适合 Cloudflare Pages + D1 + R2 部署的通用能力。

主要包括:首页 Dither 背景动画修复、真实访问统计、首页工具/书架后台管理、文章 Markdown 预览增强、R2 图片上传,以及删除内容时自动清理未再被引用的 R2 图片。

主要改动

  • 修复 Dither 背景在部署后不自动漂移的问题:在 useFrame 中更新实际挂载到 ShaderMaterial 上的 uniforms,并标记 uniformsNeedUpdate。
  • 新增真实访问统计:通过 /api/analytics/view 写入 Cloudflare D1,后台 Dashboard 显示真实 PV、UV 和趋势数据。
  • 新增 HomePage 后台分组:支持在后台管理首页 Tools 和 Books,包括排序、启用/停用、图标选择、书籍封面等。
  • 优化文章编辑体验:文章编辑页支持固定高度的编辑/预览双栏、滚动同步,以及更接近正式文章页的 Markdown/GFM 渲染。
  • 新增 R2 图片上传:后台支持给文章、日常、瞬间、书架封面上传本地图片。
  • 删除内容时自动清理 R2:只删除已经不再被其他内容引用的 /api/assets/... 图片,避免误删复用图片。
  • D1 表结构由运行时代码自动 CREATE TABLE IF NOT EXISTS,不需要额外提交 SQL 文件。

Cloudflare 配置

需要配置以下绑定或环境变量:

  • DB:Cloudflare D1 数据库绑定。
  • R2_ASSETS:Cloudflare R2 存储桶绑定,用于图片上传和读取。
  • ADMIN_PASSWORD:后台登录密码。
  • ANALYTICS_SALT:可选,用于访问者哈希的 salt;不配置时会回退使用 ADMIN_PASSWORD。

R2 删除策略

R2 清理采用“引用感知”策略:

  • 删除内容前,先从目标内容中提取本站图片地址。
  • 只识别 /api/assets/... 或完整域名中的 /api/assets/... 图片。
  • 删除 D1 内容记录后,再扫描剩余文章、日常、瞬间和书架。
  • 如果同一个 R2 key 仍被其他内容引用,则保留。
  • 只有无人引用的 R2 对象才会被删除。
  • 外链图片不会被删除。
  • R2 删除失败不会阻断内容删除,清理结果会作为附加信息返回。

0de1l added 10 commits June 7, 2026 20:13
The homepage WebGL frame loop was drawing, but production sampling showed the fragment shader's time uniform only reached the GPU at initialization. Updating the memoized uniform object was not enough for the mounted ShaderMaterial, so the frame loop now writes to the live material uniforms and marks them for upload.

Constraint: Preserve the original MiniLoad shader and visual style
Rejected: Increase wave speed again | frame sampling showed the deployed issue was uniform upload, not motion magnitude
Confidence: high
Scope-risk: narrow
Directive: Verify future animation fixes by sampling the canvas frame and uniform updates before changing shader visuals
Tested: npm run build; production Chrome canvas sampling showed time uniform advancing and background frame hashes changing
Not-tested: Cloudflare Pages redeploy runtime
The admin dashboard previously displayed fixed demo traffic numbers. This adds a lightweight first-party analytics event table, client-side pageview reporting, and an authenticated admin summary endpoint so deployed traffic metrics come from Cloudflare D1 instead of hardcoded arrays.

Constraint: Cloudflare Pages uses D1 binding name DB and Edge API routes.

Rejected: External analytics service | harder to surface inside the existing admin dashboard and adds another service dependency.

Rejected: Store raw IP addresses | unnecessary for visit counting and worse for privacy.

Confidence: high

Scope-risk: moderate

Directive: Keep /admin and /api paths excluded from tracking or admin refreshes will pollute traffic metrics.

Tested: npm run build

Tested: npx tsc --noEmit

Tested: scoped eslint on analytics routes, tracker, layout, admin page, and auth route

Not-tested: Cloudflare Pages runtime D1 write/read after deployment
The homepage tools and bookshelf were hardcoded in the page component, so changing them required code edits and redeploys. This moves those modules behind D1-backed read/write APIs, keeps the current homepage content as default seed data, and adds a HomePage admin section with Tools and Books editors.

Constraint: First version uses cover URLs only; local upload/R2 storage is intentionally left out.

Constraint: Cloudflare Pages D1 binding is expected to remain DB.

Rejected: Commit a manual SQL migration file | schema is created lazily by the runtime and the user asked to exclude the SQL file.

Rejected: Store React icons in D1 | database stores stable icon keys and the UI maps them to Feather icons.

Confidence: high

Scope-risk: moderate

Directive: Keep home-module shared UI constants separate from server-only D1 helpers so Client Components do not import Cloudflare server-only code.

Tested: npm run build

Tested: npx tsc --noEmit

Tested: scoped eslint on home module APIs, home module libs, homepage, and admin page

Not-tested: Cloudflare Pages runtime D1 seed/write/delete after deployment
The post editor preview panel grew with rendered content while the source panel used its own scroll area, making long Markdown editing hard to compare. This fixes the preview panel height, lets it scroll internally, syncs preview scroll from source scroll by ratio, and keeps HomePage collapsed by default in the admin nav.

Constraint: Keep the implementation lightweight without Markdown AST source-to-preview mapping.

Rejected: Bidirectional scroll sync | can cause feedback loops and awkward jumps because Markdown source height and rendered preview height are not linearly equivalent.

Confidence: high

Scope-risk: narrow

Directive: If precise preview alignment becomes necessary, add block-level Markdown position mapping instead of making this ratio sync more complex.

Tested: npx eslint src/app/admin/page.tsx

Tested: npx tsc --noEmit

Tested: npm run build

Not-tested: Manual browser scroll feel in deployed admin
The admin sidebar used broad transition-all behavior and abrupt label mounting, which made expand/collapse feel janky. This narrows the animated properties, applies an ease-out cubic bezier curve to the sidebar and nav items, and removes the committed analytics SQL reference file because runtime schema creation already owns that migration path.

Constraint: Analytics schema is still created lazily by ensureAnalyticsSchema at runtime.

Rejected: Keep committed analytics-schema.sql | it is redundant with runtime CREATE TABLE IF NOT EXISTS behavior and the user asked to remove it from GitHub.

Confidence: high

Scope-risk: narrow

Directive: Keep schema creation in code authoritative unless a formal migration system is introduced.

Tested: npx eslint src/app/admin/page.tsx

Tested: npx tsc --noEmit

Tested: npm run build

Not-tested: Manual browser feel on deployed admin sidebar
The post editor preview used a small regex renderer, so code fences and other Markdown features did not match published posts. This adds a remark/rehype preview renderer, reuses the MarkdownContent enhancement layer for code blocks, and keeps source-to-preview scroll sync in the editor.

Constraint: Admin preview now carries the Markdown renderer in the admin client bundle.

Rejected: Add only CSS for pre/code blocks | would still leave preview behavior inconsistent with published posts.

Rejected: Server-render preview API for this pass | lower bundle size but adds debounce/request state and is unnecessary for the current admin-only surface.

Confidence: high

Scope-risk: moderate

Directive: If admin load time becomes a problem, move renderMarkdownPreviewHtml behind an authenticated preview API instead of reverting to regex parsing.

Tested: npx eslint src/app/admin/page.tsx src/components/markdown-content.tsx src/lib/markdown-preview.ts

Tested: npx tsc --noEmit

Tested: npm run build

Not-tested: Manual deployed admin preview against every Markdown extension
The HomePage Books editor only accepted externally hosted cover URLs. Cloudflare Pages now has an ASSETS R2 binding, so the admin can upload local cover images, store them in R2, and persist the returned /api/assets URL in the existing cover field.

Constraint: R2 binding name is ASSETS and admin authentication continues to use the Authorization header with ADMIN_PASSWORD.

Constraint: First pass supports Books cover uploads only; other image fields can reuse the upload endpoint later.

Rejected: Public R2 bucket URLs | proxying through /api/assets keeps the bucket private and avoids adding a custom public bucket domain now.

Confidence: high

Scope-risk: moderate

Directive: Keep uploads behind admin auth and do not expose direct R2 credentials to the browser.

Tested: npx eslint src/app/admin/page.tsx src/app/api/admin/upload/route.ts src/app/api/assets/[...key]/route.ts

Tested: npx tsc --noEmit

Tested: npm run build

Not-tested: Live Cloudflare Pages R2 write/read after deployment
Cloudflare Pages failed while publishing the Function after the build and asset upload completed. The R2 bucket was exposed as ASSETS, which overlaps with the common Pages/Workers static asset binding name, so the upload and read routes now use an explicit R2_ASSETS binding instead.

Constraint: Cloudflare Pages Functions may reserve ASSETS for static assets.\nRejected: Keep ASSETS binding | publish can fail or collide with static asset access.\nConfidence: high\nScope-risk: narrow\nDirective: R2 upload/read routes expect the binding name R2_ASSETS; keep the Cloudflare dashboard binding in sync.\nTested: npx eslint src/app/api/admin/upload/route.ts src/app/api/assets/[...key]/route.ts\nTested: npx tsc --noEmit\nTested: npm run build\nNot-tested: Cloudflare Pages redeploy after dashboard binding rename
The R2 upload flow was already working for book covers, so the admin editor now reuses that same endpoint for posts, daily entries, and moments instead of keeping those sections URL-only. Post uploads insert Markdown image syntax at the cursor, while daily and moment uploads fill their Image URL fields.

Constraint: Keep the existing /api/admin/upload endpoint and R2_ASSETS binding unchanged.\nRejected: Add separate upload APIs per content type | duplicates auth, validation, and R2 handling.\nConfidence: high\nScope-risk: narrow\nDirective: Keep future image upload surfaces routed through uploadImageAsset unless storage requirements diverge.\nTested: npx eslint src/app/admin/page.tsx\nTested: npx tsc --noEmit\nTested: npm run build\nNot-tested: Browser upload interaction against live Cloudflare R2 for post/daily/moment
Admin deletes now collect local /api/assets references from the deleted record, remove the database row, then scan remaining posts, daily entries, moments, and books before deleting only unreferenced R2 objects. The cleanup result is returned as metadata so content deletion stays successful even if asset cleanup is skipped or partially fails.

Constraint: R2 objects can be reused across content, so deletion must be reference-aware.\nRejected: Delete every image URL found in the removed content | risks deleting assets still used elsewhere.\nConfidence: high\nScope-risk: moderate\nDirective: Keep asset cleanup limited to /api/assets URLs; never delete external image URLs.\nTested: npx eslint src/lib/r2-assets.ts src/app/api/admin/delete/route.ts src/app/api/admin/home-items/route.ts\nTested: npx tsc --noEmit\nTested: npm run build\nNot-tested: Live R2 deletion against production bucket
@0de1l 0de1l changed the title 修复 Dither 背景动画部署后不自动漂移的问题 完善 D1 后台管理、真实访问统计、R2 图片上传,并修复 Dither 动画 Jun 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant