墨·码
随笔

关于本站

「墨 · 码」记录我在 Web 开发中的学习、实践与思考,以及对相关技术的探索与验证。

1384 字7 分钟加载中
关于本站

「墨 · 码」是我的个人技术博客,主要记录 Web 开发中的学习、实践与思考。这里既有从入门到实践的学习笔记,也有开发过程中遇到的问题与解决方案。

写作对我来说也是学习的一部分。把学到的知识重新梳理,再用自己的语言表达出来,往往能发现原本遗漏或理解模糊的地方,也能让零散的知识逐渐建立起联系。与此同时,自己搭建和维护博客,也让我有了更多实践 Web 技术的机会。

本站最初参考了 QuietPages 的设计思路。随着需求逐渐变化,我也在持续调整站点的设计与功能。这个博客不只是记录学习的地方,它本身也成为了我学习和实践 Web 开发的一部分。

页面概览

站点围绕内容阅读与信息展示,提供了文章浏览、内容归档、数据统计、联系交流、版权说明等页面。

内容浏览

首页 主要展示最新内容和精选文章,方便快速了解近期更新。Hero 区域提供背景切换功能,可选择 Unsplash 或 Lorem Picsum 作为图片来源。

文章列表 用于浏览和搜索文章,并可以按照分类、标签和合集进行筛选,同时提供排序和分页功能。进入文章详情页后,可以通过上下篇导航和相关文章继续延伸阅读,也可以使用 Giscus 评论区参与讨论。

归档 按照时间整理全部文章,并提供年、月和列表三种视图:年视图用于查看各年份的文章分布和更新频率;月视图通过日历展示具体日期的文章发布情况;列表视图则按照发布时间分组展示文章,适合按时间顺序浏览和回顾。

站点信息

数据统计 汇总文章数量和字数、发布活跃度、分类构成,以及访问趋势、访客地域等数据。页面中的数据图表基于 ECharts 实现,地域分布则通过地图展示。配置高德安全密钥后使用 高德地图 JS API 2.0,未配置时则使用 Leaflet 加载 CARTO 提供的地图瓦片。

联系 提供站内邮件发送功能,支持发送前预览邮件内容,并通过 Resend 完成邮件发送。

版权声明 记录站内字体、图片、纹理及第三方资源的来源与许可信息。

站外资源

收藏 用于整理常用的网站,并按照类别进行分组,支持搜索和筛选。

友链 用于展示长期关注的个人站点,同时提供友链提交入口。

视觉与浏览体验

站点的视觉效果由多组设置共同控制:皮肤负责切换主题配色,明暗模式用于调整整体明度,纹理则为页面背景增加细节与质感。此外,标题和正文可以分别选择字体,字体资源由 ZeoSeven Fonts 提供,正文字号也可以根据阅读习惯进行调整。

Rough Notation 为标题、按钮和重点内容添加手绘标注。当页面布局发生变化时,标注会重新计算位置。

站内还加入了少量 UI 音效,用于配合页面交互。音效会尽量与交互过程保持同步,但受音频加载、浏览器调度和系统播放链路等因素影响,首次播放或长时间未触发后再次播放时,可能会出现较明显的延迟。

站内导航通过 Astro ClientRouter 实现客户端路由,并基于 View Transition 定制页面切换动画,减少页面跳转时的视觉割裂感。

页面使用 Lenis 实现平滑滚动。目录、评论区、编辑器和设置面板等具有独立滚动区域的组件则保留原生滚动,避免滚动行为相互干扰。

代码实验

文章中的代码内容可以分为三种形式:行内代码、多行代码和可运行代码。

行内代码通过 rehype-pretty-code 处理。插件会读取代码中的语言标记,并调用 Shiki 进行语法解析,最终生成带有 token 样式的 HTML。

语言标记写在行内代码末尾,例如:`const count = 1{:javascript}` 会被渲染成 const count = 1。指定语言后会按对应语法进行高亮,未指定语言的行内代码则保留基础样式。

需要展示多行代码时,使用带语言标记的 Markdown 代码块,例如:```javascript。它与行内代码使用同一个高亮引擎,但处理入口不同,不会经过 rehype-pretty-code 的行内节点转换。

const count = 1;
console.log(count);

文章也可以通过 MDX 嵌入可运行的代码实验组件,读者可以直接修改代码并实时运行。这些组件均由文章页面统一注入,在 MDX 中使用时无需重复导入。

前端 Playground

前端 Playground 基于 Sandpack 实现,主要用于运行和演示 HTML、CSS、JavaScript 以及前端组件等浏览器端代码。它支持多文件编辑,并提供实时预览、控制台输出、代码格式化、布局调整等功能。每个示例还可以指定默认打开的文件,以及初始化时显示预览或控制台。

<Playground
  codeId="about/frontend-playground"
  title="前端实时预览"
  activeFile="/App.js"
  initialPane="result"
/>

下面是可编辑和运行的示例,修改左侧文件后,可以在右侧的预览界面或控制台中查看运行结果。

前端实时预览准备中正在读取代码文件,请稍候…

远程代码运行器

远程代码运行器使用 CodeMirror 编辑源码,并将运行请求转发至访客选择的远程执行平台,例如: Judge0 和 Wandbox。它适合运行 Python、Java、C++ 等需要解释器或编译器的代码。

<RemoteCodeRunner
  codeId="about/python-remote-execution"
  title="Python 远程执行"
  language="python"
  stdin="Ink · Code"
/>

下面是可编辑和运行的示例,配置标准输入并运行代码,可以在输出面板中查看运行结果。

远程代码执行

Python 远程执行

布局
main.py40 字符

选择平台并点击“运行”,结果会显示在这里。

Warning

为避免远程执行服务被滥用,部署时应妥善保管各平台的 API Key、Token 等凭据,不要将其暴露在浏览器端或提交到公开仓库。

不同执行平台有各自的调用配额和服务限制,达到限额后,代码可能无法正常运行,此时可以切换到其他执行平台后再次尝试。

浏览器端 SQL Playground

浏览器端 SQL Playground 基于 sql.js 和 WebAssembly 在浏览器中运行 SQLite,无需连接远程数据库。它支持连续的增删改查操作,也可以根据需要只运行当前选中的 SQL 片段。

为了便于演示常见的 MySQL 示例,我在 SQLite 执行前增加了一层轻量兼容处理,将部分 MySQL 语法转换为 SQLite 可执行的形式。对于兼容层无法处理的语句,则直接交由 SQLite 执行;如果其中包含 SQLite 不支持的语法,就会返回相应错误。

多个组件使用相同的 databaseId 时,可以共享同一个浏览器端数据库。数据库快照会保存在当前浏览器的 IndexedDB 中,适合在不同文章或章节之间连续进行实验。

未设置 databaseId 时,每个组件都会使用独立的临时数据库,适合运行相互独立、无需保留状态的 SQL 示例。

<SqlPlayground
  codeId="about/sqlite-local-execution"
  title="SQLite 本地执行"
/>

下面是可编辑和运行的示例,可以一次执行多条语句,也可以执行选中的部分 SQL 语句。

浏览器端 SQL 执行

SQLite 本地执行

布局
query.sql195 字符
执行结果

项目结构

下面列出了项目的主要目录和关键文件,并简要说明它们各自的用途。

项目结构
  • src站点的主要源码目录。
    • components站点使用的各类功能组件。
    • contentAstro Content Collections 使用的文章与结构化数据。
  • public无需经过构建处理、可直接发布的静态资源。
  • migrationsCloudflare D1 数据库迁移文件。
  • scripts文章创建和本地开发使用的辅助脚本。

站点配置

站点信息

站点的基础配置信息集中在 src/configs/site.ts:

const siteConfig = {
  name: "墨·码",
  description: "基于 Astro 构建的个人技术博客,记录技术探索与开发日常",
  url: "https://example.com",
  author: {
    email: "you@example.com",
    location: "你的城市",
    coordinates: [30.63, 104.0], // [纬度, 经度]
  },
  clientRouter: true,
  codeTheme: {
    light: "catppuccin-latte",
    dark: "one-dark-pro",
  },
  giscus: {},
  nav: [],
  footerNav: [],
};

国际化

国际化配置位于 src/configs/i18n.ts。

const i18nConfig = {
  enabled: true,
  defaultLocale: "zh-CN",
  locales: ["zh-CN", "en", "ja"],
  fallback: {
    en: "zh-CN",
    ja: "zh-CN",
  },
  routing: {
    prefixDefaultLocale: false,
  },
};
  • enabled:决定是否开启国际化,关闭后,全站只使用 defaultLocale;
  • defaultLocale:决定站点的默认语言,以及未带语言前缀的 URL 使用哪种语言;
  • locales:决定站点支持哪些语言,语言代码会用于 URL 前缀。新增语言时,需要同时扩展 src/types/i18n.ts 中的 SUPPORTED_LOCALES 和文案字典;
  • fallback:决定某种语言缺少文章译文时跳转到哪种语言的 URL。例如 { en: "zh-CN" } 表示英文译文不存在时跳转到对应的中文页面;没有配置回退映射时直接返回 404;
  • prefixDefaultLocale:控制默认语言是否使用 /<locale>/ 前缀;

地图服务

地图统一由 src/configs/map.ts 配置。本地开发时,浏览器需要的公开配置放在项目根目录的 .env,Worker 请求运行时使用的安全密钥放在 .dev.vars;两个文件的职责不同,不要把同一个变量重复填写。Cloudflare 生产环境的配置位置见下方 部署到 Cloudflare 一节:

.env:构建期和浏览器需要的公开配置。

PUBLIC_AMAP_KEY=你的高德 Web API Key
PUBLIC_CARTO_KEY=你的 CARTO Basemap Key

.dev.vars:本地 Worker 运行期 Secret。

AMAP_SECURITY_CODE=你的高德安全密钥

PUBLIC_AMAP_KEY 是 高德地图 JS API 2.0 的 Web 端 Key,必须随浏览器请求发送。AMAP_SECURITY_CODE 是高德安全密钥,本地和生产都由 /_AMapService 同域代理在服务端读取,不会暴露给浏览器。

没有配置 PUBLIC_AMAP_KEY 时,组件自动回退到 Leaflet 和 CARTO。如果已经配置 Key 但缺少 AMAP_SECURITY_CODE,高德代理请求会失败,需要先补齐该变量。

PUBLIC_CARTO_KEY 用于 CARTO 底图服务,地图瓦片请求会在浏览器中携带它,因此它同样属于公开 Key。建议在 CARTO 申请并配置它,以避免地图被添加水印。

部署到 Cloudflare

项目使用 @astrojs/cloudflare 构建 Cloudflare Workers,并在 wrangler.jsonc 中声明 Worker、静态资源和 D1 数据库绑定。

本项目通过 Cloudflare Workers 的 GitHub 集成,实现自动部署。连接仓库后,向指定分支推送代码,Cloudflare 会自动执行构建和部署。具体配置可以参考 Workers Builds 文档。

首次配置

  1. 创建属于自己的 D1 数据库

    pnpm exec wrangler login
    pnpm exec wrangler d1 create ink-code-analytics

    将命令返回的 database_id 和数据库名称写入 wrangler.jsonc,注意保留 ink_code_analytics 这个 binding。

  2. 在 Cloudflare 控制台连接 GitHub 仓库

    • 新建 Worker:Workers & Pages → Create application → Import a repository;
    • 已有 Worker:进入 Worker 的 Settings → Builds → Connect;
    • 选择本仓库和生产分支,根目录填写 /;
    • 构建命令填写 pnpm run build,部署命令填写 npx wrangler deploy;
    • 确认 Cloudflare 中的 Worker 名称为 ink-code,与 wrangler.jsonc 中的 name 一致。
  3. 配置环境变量

    环境变量分为 “构建期” 和 “Worker 运行期” 两类。两类变量的读取时机不同,必须分别配置:

    • 构建期变量:由 Astro/Vite 在执行 pnpm run build 时读取 import.meta.env。本地放在 .env;Cloudflare 放在 Worker → Settings → Builds → variables and secrets。这里配置:
      • SITE_URL:生产站点地址;
      • PUBLIC_AMAP_KEY、PUBLIC_CARTO_KEY、PUBLIC_AMAP_SERVICE_HOST、PUBLIC_HERO_IMAGE_PROVIDER:会进入浏览器代码或请求地址的公开配置。PUBLIC_* 不能存放 Secret;
    • Worker 运行期变量:Worker 收到请求后从 cloudflare:workers 的 env 读取。本地放在 .dev.vars;Cloudflare 放在 Worker → Settings → Runtime variables and Secrets。这里配置:
      • RESEND_API_KEY、RESEND_FROM_EMAIL:联系接口在请求运行时读取,API Key 保存为 Secret;
      • AMAP_SECURITY_CODE:高德安全密钥,由 /_AMapService 代理读取并添加到上游请求;
      • UNSPLASH_ACCESS_KEY:由 /api/hero-image/ 读取,浏览器不会拿到该 Key;随机图片接口不需要 UNSPLASH_SECRET_KEY;
      • CODE_RUN_RATE_LIMIT_SALT:代码运行器限流盐值,保存为 Secret;
      • 代码运行平台凭据和运行时端点:完整变量名见仓库根目录的 .dev.vars.example,凭据全部保存为 Secret。

    构建期变量不会自动成为 Worker 的运行时 env,运行期变量也不会参与 Astro/Vite 构建。PUBLIC_* 值会被编译进浏览器代码或请求地址,只能使用公开 Key;任何 API Token、Client Secret、密码和盐值都不要添加 PUBLIC_ 前缀。

    本地推荐同时使用两个文件:.env 只放构建期和公开配置,.dev.vars 只放 Worker 运行期配置与 Secret。按照 Cloudflare 本地环境变量文档 的规则,存在 .dev.vars 时,Wrangler 不会再把 .env 的值合并到 Worker env;因此同一个变量不能同时写在两个文件中。如果不创建 .dev.vars,也可以只用 .env 作为本地构建和运行时的单一来源,但所有运行期变量都必须放进 .env,不适合需要明确隔离 Secret 的配置。

  4. 首次部署前,将迁移应用到远程 D1。GitHub 自动部署只负责构建和发布 Worker,不会自动执行数据库迁移:

    pnpm exec wrangler d1 migrations apply ink-code-analytics --remote

    本地开发时可以先使用 --local 验证。后续新增迁移时,也要在确认 SQL 无误后再次执行 --remote; 已应用的迁移不要修改或删除,只新增更大的编号。详细规则见 D1 migrations 文档。

日常发布

修改代码后运行 pnpm check,提交并推送到 Cloudflare 监听的分支即可:

git add .
git commit -m "更新站点"
git push origin main

Cloudflare 会自动执行构建和部署。构建失败时,先在 Worker 的 Deployments → View build history 查看日志。

资源与许可

资源的许可和使用说明以 版权页 为准。如果发现资源来源或使用方式存在问题,可以通过 联系页 告知我。