项目结构分析

项目结构

📦src
┣ 📂components
┃ ┣ 📜Banner.astro
┃ ┣ 📜BaseHead.astro
┃ ┣ 📜Footer.astro
┃ ┣ 📜GlobalStyles.astro
┃ ┣ 📜Markdown.astro
┃ ┣ 📜MobileSearchBar.svelte
┃ ┣ 📜NavBar.astro
┃ ┣ 📜PostCard.astro
┃ ┣ 📜ScriptSetup.astro
┃ ┣ 📜SearchBar.svelte
┃ ┗ 📜SideBar.astro
┣ 📂contents
┃ ┣ 📂posts
┃ ┗ 📂specs
┣ 📂layouts
┃ ┣ 📜BaseLayout.astro
┃ ┣ 📜ChipLayout.astro
┃ ┣ 📜MainLayout.astro
┃ ┣ 📜PostArchiveLayout.astro
┃ ┗ 📜PostLayout.astro
┣ 📂locales
┣ 📂pages
┃ ┣ 📂categories
┃ ┃ ┣ 📜index.astro
┃ ┃ ┗ 📜[category].astro
┃ ┣ 📂posts
┃ ┃ ┗ 📜[...slug].astro
┃ ┣ 📂tags
┃ ┃ ┣ 📜index.astro
┃ ┃ ┗ 📜[tag].astro
┃ ┣ 📜about.astro
┃ ┣ 📜archive.astro
┃ ┗ 📜[...page].astro
┣ 📂plugins
┣ 📂styles
┣ 📂types
┣ 📂utils
┣ 📜content.config.ts
┗ 📜env.d.ts

assets

  • 作用: 存放需要 Astro 构建和优化的静态资源。
  • 分析: 这里的图片在 npm run build 时会被处理,例如压缩或添加哈希文件名,以获得更好的性能和缓存策略。

components - UI积木盒

  • 作用: 存放所有可复用的前端组件。
  • 分析:
    • controllers: 存放交互控制类组件,如 Pagination.astro(翻页器)。
    • misc: 存放其他杂项组件,如 ArchivePost.astro(归档页面的文章条目)、CopyRight.astro(版权声明)。
    • widgets: 存放一些小挂件,如 SocialIcon.astro(社交媒体图标)。
    • 根组件: NavBar.astro(导航栏)、Footer.astro(页脚)、PostCard.astro(文章卡片)是构成页面的主要视觉元素。
    • 特殊组件:
      • BaseHead.astro: 专门用来管理 标签里的内容(如SEO相关的meta标签、标题等),非常好的实践。
      • SearchBar.svelte, MobileSearchBar.svelte: 关键文件。这两个是用 Svelte 写的交互式组件。Astro 会为它们单独加载 JavaScript,实现客户端的动态搜索功能,而不会影响其他页面的静态性。这就是 Astro Islands 的精髓。

📂contents - 内容仓库

  • 作用: 存放网站的所有原始内容,主要是 Markdown 文件。
  • 分析:
    • posts: 所有的博客文章。注意,文章的配图 (image-1.png) 和文章 (.md) 放在一起,这被称为 Co-location,便于管理。
    • specs: 可能存放一些特殊的、独立的页面内容,比如 about.md 就是“关于我”页面的内容源。

📂layouts - 页面骨架

  • 作用: 定义不同类型页面的通用布局。
  • 分析:
    • BaseLayout.astro: 最基础的布局,可能只包含 <html>, <head>, <body> 和一些全局脚本/样式。
    • MainLayout.astro: 用于主页面(如首页、归档页)的布局,可能在 BaseLayout 的基础上增加了导航栏和页脚。
    • PostLayout.astro: 专门用于渲染单篇文章页面的布局。
    • PostArchiveLayout:专门用于将分类列表渲染为时间线形式

📂locales - 国际化中心

  • 作用: 管理网站的多语言翻译。
  • 分析:
    • languages: 存放具体的语言文件,en.ts 是英文翻译,zh_cn.ts 是中文翻译。它们通常是一个 key-value 对象,如 { “nav.home”: “首页” }。
    • keys.ts: 可能定义了所有翻译的 key,方便类型检查和自动补全。
    • translation.ts: 包含了获取当前语言、根据 key 查找翻译文本的工具函数。

📂pages - 网站路由地图

  • 作用: 目录结构直接映射成网站的URL。
  • 分析:
    • [...page].astro: 首页分页。[…page] 是一个可选的 rest 参数,site.com/ 会匹配到它,site.com/2 也会匹配到它,从而实现 /, /2, /3 这样的分页 URL。
    • posts/[...slug].astro: 渲染单篇博客文章的页面。
    • categories/ & tags/: 分别用于展示分类和标签下的文章列表,index.astro 是列表首页,[category].astro[tag].astro 是具体分类/标签下的文章列表页。
    • archive.astro: 归档页面 (/archive)。
    • rss.xml.ts, robots.txt.ts: 动态文件生成。这些是以 .ts 结尾的 API 端点。当构建时,Astro 会执行它们并将其输出保存为 rss.xml 和 robots.txt 文件,而不是生成 HTML 页面。这是生成 RSS 订阅源和 SEO 文件的标准方式。

📂plugins - Markdown 魔法棒

  • 作用: 存放自定义的 Remark/Rehype 插件,用于在构建时增强 Markdown 的功能。
  • 分析:
    • remark-reading-time.mjs: 一个插件,用于读取文章内容并计算出大致的阅读时间。
    • remark-toc.mjs: 一个插件,用于根据文章的标题(H1, H2…)自动生成目录 (Table of Contents)。

📂styles - 全局调色板

  • 作用: 存放全局 CSS 文件。
  • 分析: 按功能拆分 CSS 文件(动画、Markdown样式、滚动条等),然后在 GlobalStyles.astro 组件或布局文件中统一引入,结构清晰。

📂types & 📂utils - 内部工具箱

  • types: 存放 TypeScript 的类型定义。config.ts 可能定义了整个网站配置对象的类型。
  • utils: 存放可复用的工具函数。
    • content.ts: 核心工具。很可能包含了获取、排序、过滤所有博客文章 (contents/posts) 的逻辑。
    • date.ts: 格式化日期的函数。

根目录下的 src 文件

  • content.config.ts: 内容集合的“宪法”。它与 contents 目录紧密配合,使用 Zod 来定义每篇 post 和 spec 的 frontmatter 必须包含哪些字段(如 title, pubDate),以及这些字段的类型。如果某篇文章的 frontmatter 不符合这里的定义,Astro 在构建时会报错。
  • env.d.ts: TypeScript 的环境声明文件,用于让 TypeScript 识别 Astro 的一些内置类型。

工作流程串联

  1. 构建开始: npm run build。
  2. 内容校验: Astro 读取 content.config.ts,然后扫描 contents 目录,确保所有 .md 文件都符合规范。
  3. 路由生成: Astro 查看 pages 目录。
    • 它发现 posts/[…slug].astro,于是执行其 getStaticPaths 函数。该函数很可能调用了 utils/content.ts 里的函数来获取所有文章,为每篇文章注册一个 URL。
    • 在处理 Markdown 内容时,会应用 plugins 里的插件,为每篇文章附加阅读时间和目录数据。
  4. 页面渲染:
    • 对于每篇文章页面,Astro 使用 PostLayout.astro 布局。
    • 布局中会用到 NavBar.astro, Footer.astro 等组件。
    • 如果页面需要显示多语言文本(比如导航栏的“首页”),会通过 locales 里的工具函数获取对应的翻译。
  5. 交互注入: 当渲染到 SearchBar.svelte 组件时,Astro 会把它标记为一个“岛屿”,并为其打包单独的 JS 文件,以便在浏览器中激活交互功能。
  6. 静态文件生成: rss.xml.ts 被执行,生成 RSS 文件。所有页面被渲染成最终的 .html 文件,连同 assets 里的优化后资源,一起放入 dist/ 目录,等待部署。

pages/目录

src/pages/ 目录是 Astro 的路由核心

src/pages/about.astro -> yourdomain.com/about
src/pages/index.astro (或 [...page].astro) -> yourdomain.com/
src/pages/posts/[...slug].astro -> yourdomain.com/posts/你的文章标题

以一例子介绍新建板块

建立一个新的板块叫friends,并且将它和自身的URL链接一起

  1. 创建页面并链接 URL
  2. 将它添加到导航栏
第1步:创建页面文件 (friends.astro)
  1. 创建文件: 在 src/pages/ 目录下,直接创建一个新文件,命名为 friends.astro。
    • 文件名就是 URL。这个文件的路径是 src/pages/friends.astro,所以 Astro 会自动为它生成 yourdomain.com/friends 这个网址。
  2. 编写页面代码
第2步:将 “友链” 链接添加到导航栏
  1. 找到导航栏组件: 导航栏组件在 src/components/NavBar.astro
  2. 添加链接: 打开 yukina.config.ts 文件,找到导航链接的列表 navigators,在其中添加一行指向 /friends 的链接。
  3. 记得在 locales 里的几个文件新建变量,如nav_bar_friends

/utils/content.ts

​ 整个博客的**“数据处理中心”**。网站的各个页面(如首页、归档页、标签页)并不直接去 src/content/ 目录下“生硬地”拉取原始数据,而是调用这个文件里提供的“加工好”的函数,来获取它们需要的数据格式。


总体功能

这个文件的核心作用是:

  1. 从Astro的内容集合中获取所有的博客文章。
  2. 对这些文章进行筛选(例如,在生产环境中过滤掉草稿)、排序(按日期)、分组(按年份、按标签、按分类)。
  3. 将原始的文章数据,处理成特定页面需要的、结构化的数据格式(例如,一个按年份组织的归档列表)。

代码结构分析

  1. 类型定义

    1. Archive: 代表一个简化的文章对象。注意,它只包含了 title, id, date, tags 这几个核心信息。它不包含文章的完整内容(body),因此非常适合用在列表、归档等不需要显示全文的场景,可以减小数据处理量。
    2. Tag: 代表一个标签。它包含标签名(name)、URL路径(slug),以及一个Archive对象数组(posts),这个数组里存放了所有打了这个标签的文章。
  2. 核心函数详解

    1. GetSortedPosts()

      • 目的: 获取所有博客文章,并按发布日期降序(最新的在前)排列好。

      • 执行流程:

        1. 调用 getCollection("posts", ...) 获取所有文章,并通过一个回调函数过滤掉草arg稿 (data.draft !== true)。import.meta.env.PROD 是Astro提供的环境变量,用于判断当前是否是生产构建环境。
        
        2. 使用 .sort() 方法对所有文章进行排序。
        
        3. 一个非常贴心的功能:通过两个 for 循环,为每一篇文章对象动态添加了 nextSlug, nextTitle, prevSlug, prevTitle 这四个属性。
      • 输出: 一个包含完整文章对象的数组。这里的“完整”指的是 getCollection 返回的原始对象,包含了 data (frontmatter), body, slug 等所有信息。

      • 用途: 主要用在文章详情页 (/posts/[…slug].astro)。通过这四个附加属性,可以轻松实现“上一篇”和“下一篇”的导航链接,而无需在页面上再次计算。

    2. GetArchives()

      • 目的: 获取所有文章,并按年份进行分组,用于生成“归档”页面。
      • 执行流程:
        1. 同样是获取并过滤所有文章。
        2. 创建一个 Map 对象 archives。Map 是一种键值对集合,这里用来存储 年份 -> 文章列表 的关系。
        3. 遍历所有文章,获取每篇文章的年份。
        4. 如果 archives 中还没有这个年份的键,就创建一个空数组。
        5. 将当前文章处理成一个简化的 Archive 对象,然后推入对应年份的数组中。
        6. 最后,对年份(Map的键)和每个年份内的文章(Map的值)都进行降序排序。
      • 输出: 一个 Map 对象。键是年份(number),值是该年份下的Archive对象数组
      • 用途: 归档页 (/archive.astro)。这个页面会遍历这个Map,先渲染年份标题,再渲染该年份下的文章列表,形成时间线视图。
    3. GetTags()

      • 目的: 提取出所有文章中出现过的所有标签,并为每个标签整理出包含它的文章列表。

      • 执行流程:

        1. 获取并过滤所有文章。
        2. 创建一个 Map 对象 tags,用于存储 标签slug -> Tag对象 的关系。
        3. 遍历所有文章,再遍历每篇文章的 tags 数组。
        4. 对于每个标签,如果它没在 tags Map里出现过,就创建一个新的Tag对象。
        5. 将当前文章处理成一个Archive对象,推入这个标签对应的 posts 数组中。
      • 输出: 一个 Map 对象。键是标签的slug(string),值是包含该标签下所有文章的Tag对象

      • 用途:

        标签列表页 (/tags/index.astro) 和 特定标签的文章列表页 (/tags/[tag].astro)。
    4. GetCategories()

      1. 目的: 和 GetTags 完全类似,只是处理的对象是 category 字段。
      2. 执行流程: 与 GetTags 的流程几乎一模一样,只是它处理的是 post.data.category 字段。
      3. 输出: 一个 Map 对象。键是分类的slug(string),值是包含该分类下所有文章的Category对象
      4. 用途: 分类列表页 (/categories/index.astro)特定分类的文章列表页 (/categories/[category].astro)

categories/index.astro

这个文件的唯一目的是:生成一个展示所有一级分类的列表页面


以下为 yukina\src\layouts\ChipLayout.astro 组件实现的样式

alt text


  1. 数据处理(在 --- 代码块中完成):

    • 调用 GetCategories() 函数,从你所有的 .md 文章中提取出所有的一级分类。
    • 将提取出的分类数据,整理成 ChipLayout.astro 组件能够理解的格式。
  2. 内容展示(在 <ChipLayout … /> 中完成):

    • 将整理好的数据“喂”给 ChipLayout.astro 组件。
    • ChipLayout.astro 组件则负责将这些数据渲染成用户最终看到的、可点击的、带有文章数量角标的分类“芯片”。

🎯 Astro vs Svelte 的核心区别

Astro(静态站点生成器)

  • 用途:页面路由、静态内容生成、SEO优化
  • 特点:
    • 默认生成静态HTML,零JavaScript
    • 优秀的SEO和加载性能
    • 基于文件的路由系统(pages/目录结构直接映射URL)
    • 支持多种框架组件(React、Vue、Svelte等)

Svelte(客户端交互框架)

  • 用途:动态交互、状态管理、用户界面逻辑
  • 特点:
    • 编译时优化,运行时体积小
    • 响应式状态管理
    • 组件化开发
    • 真正的客户端JavaScript执行

🏗️ 在我们项目中的实际应用

Astro负责的部分:

 <!-- src/pages/admin/dashboard.astro -->

// 服务端代码:路由保护、初始数据获取

import AdminLayout from "../../layouts/AdminLayout.astro";

<AdminLayout title="管理后台">
<!-- 路由守卫脚本 -->
  <script is:inline>
  function checkAuthentication() {
    const token = localStorage.getItem('admin_token');
    if (!token) {
      window.location.href = '/admin/login';
    }
  }
  checkAuthentication();
</script>
<!-- 这里嵌入Svelte组件处理交互 -->
  <AdminDashboard client:load />

</AdminLayout>

Svelte负责的部分:

  <!-- src/components/admin/AdminDashboard.svelte -->
  <script>
    import { onMount } from 'svelte';
    import { PostsAPI } from '../../lib/admin/api.ts';
    let posts = [];
let loading = true;

// 响应式计算
$: filteredPosts = posts.filter(post => {
  // 动态过滤逻辑
});

onMount(async () => {
  // 客户端数据获取和状态管理
  posts = await PostsAPI.getAllPosts();
  loading = false;
});

function handleDelete(slug) {
  // 动态删除逻辑
}

  </script>

  <!-- 动态模板和交互 -->
  {#if loading}
    <div class="loading">加载中...</div>
  {:else}
    {#each filteredPosts as post}
      <PostCard {post} on:delete={handleDelete} />
    {/each}
  {/if}

🔄 为什么要这样分工?

  1. 性能优化
  • Astro:生成静态HTML,首屏加载快,SEO友好
  • Svelte:只在需要交互的地方加载JavaScript,避免全站SPA的性能开销
  1. 开发体验
  • Astro:处理路由、布局、认证守卫等”框架性”工作
  • Svelte:专注于交互逻辑、状态管理等”业务性”工作
  1. 技术契合度

// 我们的架构中: Astro页面 + Svelte组件 = 完美结合

// 而不是: 纯Svelte SPA = 失去Astro的静态生成优势 纯Astro = 失去复杂交互能力

📋 在管理员面板中的具体体现

功能模块技术选择原因
路由系统Astro文件路由,SEO友好
认证守卫Astro服务端渲染,安全性高
文章列表Svelte需要筛选、排序、删除等交互
文章编辑器Svelte复杂表单状态管理
登录表单Svelte表单验证、API调用

🎯 实际开发中的优势

如果只用Astro:

  • ❌ 难以处理复杂的客户端状态
  • ❌ 表单交互体验差
  • ❌ 需要大量的页面刷新

如果只用Svelte SPA:

  • ❌ 失去Astro的构建时优化
  • ❌ SEO支持差
  • ❌ 首屏加载慢

Astro + Svelte组合:

  • ✅ 静态内容快速加载
  • ✅ 动态交互体验优秀
  • ✅ 开发时各司其职,维护性强

项目Dockerfile架构

  /webTest/
  ├── backend/
   ├── app/
   ├── Dockerfile          # 简化版:只有Python环境,不处理pnpm
   ├── docker-compose.yml  # 后端独立开发用
   └── requirements.txt
  ├── yukina/                 # 前端项目
   ├── package.json
   └── pnpm-lock.yaml
  ├── nginx/
   ├── Dockerfile
   └── nginx.conf
  ├── backend.Dockerfile      # 完整版:Python+Node.js+pnpm依赖都在构建时安装
  └── docker-compose.yml      # 集成部署用(根目录)
  1. 一个项目,两个 Dockerfile

    • backend/Dockerfile: 一个简化的 Dockerfile,只安装 Python 和 Node.js 环境,不处理 pnpm 依赖。专门给 backend/docker-compose.yml 用。

    • backend.Dockerfile (在根目录): 一个完整的 Dockerfile,处理所有依赖(Python+Node),专门给根目录的 docker-compose.yml 用。

  2. 两个 docker-compose.yml

    • backend/docker-compose.yml: 用于独立开发。它使用 backend/Dockerfile,并且 command 中包含 pnpm install 来在运行时安装依赖。

    • docker-compose.yml (在根目录): 用于集成测试。它使用根目录的 backend.Dockerfile,构建一个包含所有依赖的完整镜像,command 很简单,直接启动服务。

方案的巨大优势:

  • 场景隔离: 独立开发和集成测试的环境是完全分开配置的,互不干扰。
  • 职责清晰: 每个 Dockerfile 和 docker-compose.yml 的用途都非常明确。
  • 解决了所有问题: backend/Dockerfile 因为只处理自身,没有上下文问题。根目录的 backend.Dockerfile 因为上下文是根目录,也没有上下文问题。

修改总结

  1. ✅ backend/Dockerfile(简化版)- 已删除 pnpm 相关内容
  2. ✅ backend.Dockerfile(完整版)- 已在根目录创建,包含完整的 Python+Node.js+pnpm 依赖安装
  3. ✅ docker-compose.yml(根目录)- 已修改为使用 backend.Dockerfile,构建上下文设为根目录

现在的架构

  • 后端独立开发:使用 backend/docker-compose.yml + backend/Dockerfile
  • 集成部署测试:使用根目录的 docker-compose.yml + backend.Dockerfile
Author

JuyaoHuang

Publish Date

09 - 27 - 2025