跳转到内容

Drupal & Astro

Drupal 是一个开源的内容管理工具。

要开始,你需要具备以下条件

  1. 一个 Astro 项目 - 如果你还没有 Astro 项目,我们的安装指南将帮助你快速启动并运行。

  2. 一个 Drupal 站点 - 如果你还没有设置 Drupal 站点,可以按照官方指南安装 Drupal

要从 Drupal 获取内容,你需要启用 Drupal JSON:API 模块

  1. 通过“管理”管理菜单导航到“扩展”页面 admin/modules
  2. 找到 JSON:API 模块并勾选旁边的复选框
  3. 点击“安装”以安装新模块

现在你可以通过 JSON:API 向你的 Drupal 应用程序发出 GET 请求。

要将你的 Drupal URL 添加到 Astro,请在项目根目录中创建一个 .env 文件(如果尚不存在),并添加以下变量

.env
DRUPAL_BASE_URL="https://drupal.ddev.site/"

重新启动开发服务器以在你的 Astro 项目中使用此环境变量。

默认情况下,Drupal JSON:API 端点可用于外部数据获取请求,无需身份验证。这允许你在没有凭据的情况下为你的 Astro 项目获取数据,但不允许用户修改你的数据或站点设置。

但是,如果你希望限制访问并要求身份验证,Drupal 提供了多种身份验证方法,包括

你可以将凭据添加到你的 .env 文件中。

.env
DRUPAL_BASIC_USERNAME="editor"
DRUPAL_BASIC_PASSWORD="editor"
DRUPAL_JWT_TOKEN="abc123"
...
阅读更多关于在 Astro 中使用环境变量.env 文件的信息。

你的根目录现在应包含这些新文件

  • .env
  • astro.config.mjs
  • package.json

JSON:API 请求和响应通常很复杂且深度嵌套。为了简化处理,你可以使用两个 npm 包来简化请求和响应的处理

  • JSONA:用于服务器和浏览器的 JSON API v1.0 规范序列化器和反序列化器。
  • Drupal JSON-API Params:该模块提供了一个辅助类来创建所需的查询。在执行此操作时,它还尝试尽可能使用简短形式来优化查询。
终端窗口
npm install jsona drupal-jsonapi-params

你的内容从一个 JSON:API URL 获取。

基本的 URL 结构是:/jsonapi/{entity_type_id}/{bundle_id}

URL 始终以 jsonapi 为前缀。

  • entity_type_id 指的是实体类型,例如 node、block、user 等。
  • bundle_id 指的是实体包。对于 Node 实体类型,包可以是 article。
  • 在这种情况下,要获取所有文章的列表,URL 将是 [DRUPAL_BASE_URL]/jsonapi/node/article

要检索单个实体,URL 结构将是 /jsonapi/{entity_type_id}/{bundle_id}/{uuid},其中 uuid 是实体的 UUID。例如,获取特定文章的 URL 将采用 /jsonapi/node/article/2ee9f0ef-1b25-4bbe-a00f-8649c68b1f7e 的形式。

通过向请求添加查询字符串字段来仅检索某些字段。

GET: /jsonapi/{entity_type_id}/{bundle_id}?field[entity_type]=field_list

示例

  • /jsonapi/node/article?fields[node--article]=title,created
  • /jsonapi/node/article/2ee9f0ef-1b25-4bbe-a00f-8649c68b1f7e?fields[node--article]=title,created,body

通过添加筛选器查询字符串向你的请求添加筛选器。

最简单、最常见的筛选器是键值筛选器

GET: /jsonapi/{entity_type_id}/{bundle_id}?filter[field_name]=value&filter[field_other]=value

示例

  • /jsonapi/node/article?filter[title]=Testing JSON:API&filter[status]=1
  • /jsonapi/node/article/2ee9f0ef-1b25-4bbe-a00f-8649c68b1f7e?fields[node--article]=title&filter[title]=Testing JSON:API

你可以在 JSON:API 文档中找到更多查询选项。

Astro 组件可以使用 drupal-jsonapi-params 包从你的 Drupal 站点获取数据来构建查询。

以下示例显示了一个组件,该组件对“文章”内容类型进行查询,该内容类型具有用于标题的文本字段和用于内容的富文本字段

---
import {Jsona} from "jsona";
import {DrupalJsonApiParams} from "drupal-jsonapi-params";
import type {TJsonApiBody} from "jsona/lib/JsonaTypes";
// Get the Drupal base URL
export const baseUrl: string = import.meta.env.DRUPAL_BASE_URL;
// Generate the JSON:API Query. Get all title and body from published articles.
const params: DrupalJsonApiParams = new DrupalJsonApiParams();
params.addFields("node--article", [
"title",
"body",
])
.addFilter("status", "1");
// Generates the query string.
const path: string = params.getQueryString();
const url: string = baseUrl + '/jsonapi/node/article?' + path;
// Get the articles
const request: Response = await fetch(url);
const json: string | TJsonApiBody = await request.json();
// Initiate Jsona.
const dataFormatter: Jsona = new Jsona();
// Deserialise the response.
const articles = dataFormatter.deserialize(json);
---
<body>
{articles?.length ? articles.map((article: any) => (
<section>
<h2>{article.title}</h2>
<article set:html={article.body.value}></article>
</section>
)): <div><h1>No Content found</h1></div> }
</body>

你可以在 Drupal JSON:API 文档中找到更多查询选项

通过上述设置,你现在可以创建一个使用 Drupal 作为 CMS 的博客。

  1. 一个 Astro 项目,已安装 JSONADrupal JSON-API Params

  2. 至少有一个条目的 Drupal 站点 - 对于本教程,我们建议从使用标准安装的新 Drupal 站点开始。

    在你的 Drupal 站点的内容部分,通过点击添加按钮创建一个新条目。然后,选择文章并填写字段

    • 标题: My first article for Astro!
    • 别名: /articles/first-article-for astro
    • 描述: This is my first Astro article! Let's see what it will look like!

    点击保存以创建你的第一篇文章。你可以随意添加任意数量的文章。

  1. 如果 src/types.ts 不存在,请创建它,并使用以下代码添加两个名为 DrupalNodePath 的新接口。这些接口将匹配 Drupal 中文章内容类型的字段和路径字段。你将使用它来为你的文章条目响应提供类型。

    src/types.ts
    export interface Path {
    alias: string
    pid: number
    langcode: string
    }
    export interface DrupalNode extends Record<string, any> {
    id: string
    type: string
    langcode: string
    status: boolean
    drupal_internal__nid: number
    drupal_internal__vid: number
    changed: string
    created: string
    title: string
    default_langcode: boolean
    sticky: boolean
    path: Path
    }

    你的 src 目录现在应包含新文件

    • .env
    • astro.config.mjs
    • package.json
    • 目录src/
      • types.ts
  2. src/api 下创建一个名为 drupal.ts 的新文件,并添加以下代码

    src/api/drupal.ts
    import {Jsona} from "jsona";
    import {DrupalJsonApiParams} from "drupal-jsonapi-params";
    import type {DrupalNode} from "../types.ts";
    import type {TJsonApiBody} from "jsona/lib/JsonaTypes";
    // Get the Drupal Base Url.
    export const baseUrl: string = import.meta.env.DRUPAL_BASE_URL;

    这将导入所需的库,例如用于反序列化响应的 Jsona、用于格式化请求 URL 的 DrupalJsonApiParams 以及 Node 和 Jsona 类型。它还将从 .env 文件中获取 baseUrl

    你的 src/api 目录现在应包含新文件

    • .env
    • astro.config.mjs
    • package.json
    • 目录src/
      • 目录api/
        • drupal.ts
      • types.ts
  3. 在同一个文件中,创建 fetchUrl 函数以发出 fetch 请求并反序列化响应。

    src/api/drupal.ts
    import {Jsona} from "jsona";
    import {DrupalJsonApiParams} from "drupal-jsonapi-params";
    import type {DrupalNode} from "../types.ts";
    import type {TJsonApiBody} from "jsona/lib/JsonaTypes";
    // Get the Drupal Base Url.
    export const baseUrl: string = import.meta.env.DRUPAL_BASE_URL;
    /**
    * Fetch url from Drupal.
    *
    * @param url
    *
    * @return Promise<TJsonaModel | TJsonaModel[]> as Promise<any>
    */
    export const fetchUrl = async (url: string): Promise<any> => {
    const request: Response = await fetch(url);
    const json: string | TJsonApiBody = await request.json();
    const dataFormatter: Jsona = new Jsona();
    return dataFormatter.deserialize(json);
    }
  4. 创建 getArticles() 函数以获取所有已发布的文章。

    src/api/drupal.ts
    import {Jsona} from "jsona";
    import {DrupalJsonApiParams} from "drupal-jsonapi-params";
    import type {DrupalNode} from "../types.ts";
    import type {TJsonApiBody} from "jsona/lib/JsonaTypes";
    // Get the Drupal Base Url.
    export const baseUrl: string = import.meta.env.DRUPAL_BASE_URL;
    /**
    * Fetch url from Drupal.
    *
    * @param url
    *
    * @return Promise<TJsonaModel | TJsonaModel[]> as Promise<any>
    */
    export const fetchUrl = async (url: string): Promise<any> => {
    const request: Response = await fetch(url);
    const json: string | TJsonApiBody = await request.json();
    const dataFormatter: Jsona = new Jsona();
    return dataFormatter.deserialize(json);
    }
    /**
    * Get all published articles.
    *
    * @return Promise<DrupalNode[]>
    */
    export const getArticles = async (): Promise<DrupalNode[]> => {
    const params: DrupalJsonApiParams = new DrupalJsonApiParams();
    params
    .addFields("node--article", [
    "title",
    "path",
    "body",
    "created",
    ])
    .addFilter("status", "1");
    const path: string = params.getQueryString();
    return await fetchUrl(baseUrl + '/jsonapi/node/article?' + path);
    }

    现在你可以在 .astro 组件中使用 getArticles() 函数来获取所有已发布的文章,包括每篇文章的标题、正文、路径和创建日期数据。

  5. 转到你将从 Drupal 获取数据的 Astro 页面。以下示例在 src/pages/articles/index.astro 创建一个文章登陆页面。

    导入必要的依赖项,并使用 getArticles() 从 Drupal 获取所有内容类型为 article 的条目,同时传递 DrupalNode 接口来为你的响应提供类型。

    src/pages/articles/index.astro
    ---
    import {Jsona} from "jsona";
    import {DrupalJsonApiParams} from "drupal-jsonapi-params";
    import type {TJsonApiBody} from "jsona/lib/JsonaTypes";
    import type { DrupalNode } from "../../types";
    import {getArticles} from "../../api/drupal";
    // Get all published articles.
    const articles = await getArticles();
    ---

    这个使用 getArticles() 的 fetch 调用将返回一个类型化的条目数组,可以在你的页面模板中使用。

    如果使用了相同的页面文件,你的 src/pages/ 目录现在应包含新文件

    • .env
    • astro.config.mjs
    • package.json
    • 目录src/
      • 目录api/
        • drupal.ts
      • 目录pages/
        • 目录articles/
          • index.astro
      • types.ts
  6. 向你的页面添加内容,例如标题。使用 articles.map() 将你的 Drupal 条目显示为列表中的行项目。

    src/pages/articles/index.astro
    ---
    import {Jsona} from "jsona";
    import {DrupalJsonApiParams} from "drupal-jsonapi-params";
    import type {TJsonApiBody} from "jsona/lib/JsonaTypes";
    import type { DrupalNode } from "../types";
    import {getArticles} from "../api/drupal";
    // Get all published articles.
    const articles = await getArticles();
    ---
    <html lang="en">
    <head>
    <title>My news site</title>
    </head>
    <body>
    <h1>My news site</h1>
    <ul>
    {articles.map((article: DrupalNode) => (
    <li>
    <a href={article.path.alias.replace("internal:en/", "")}>
    <h2>{article.title}</h2>
    <p>Published on {article.created}</p>
    </a>
    </li>
    ))}
    </ul>
    </body>
    </html>

使用与上面相同的方法从 Drupal 获取数据,但这次是在一个将为每篇文章创建唯一页面路由的页面上。

此示例使用 Astro 的默认静态模式,并使用 getStaticPaths() 函数创建动态路由页面文件。此函数将在构建时被调用,以生成将成为页面的路径列表。

  1. 创建一个新文件 src/pages/articles/[path].astro,并从 src/api/drupal.ts 导入 DrupalNode 接口和 getArticle()。在 getStaticPaths() 函数中获取你的数据,为你的博客创建路由。

    src/pages/articles/[path].astro
    ---
    import {Jsona} from "jsona";
    import {DrupalJsonApiParams} from "drupal-jsonapi-params";
    import type {TJsonApiBody} from "jsona/lib/JsonaTypes";
    import type { DrupalNode } from "../../types";
    import {getArticles} from "../../api/drupal";
    // Get all published articles.
    export async function getStaticPaths() {
    const articles = await getArticles();
    }
    ---

    你的 src/pages/articles 目录现在应包含新文件

    • .env
    • astro.config.mjs
    • package.json
    • 目录src/
      • 目录api/
        • drupal.ts
      • 目录pages/
        • 目录articles/
          • index.astro
          • [path].astro
      • types.ts
  2. 在同一个文件中,将每个 Drupal 条目映射到一个具有 paramsprops 属性的对象。 params 属性将用于生成页面的 URL,而 props 的值将作为 props 传递给页面组件。

    src/pages/articles/[path].astro
    ---
    import {Jsona} from "jsona";
    import {DrupalJsonApiParams} from "drupal-jsonapi-params";
    import type {TJsonApiBody} from "jsona/lib/JsonaTypes";
    import type { DrupalNode } from "../../types";
    import {getArticles} from "../../api/drupal";
    // Get all published articles.
    export async function getStaticPaths() {
    const articles = await getArticles();
    return articles.map((article: DrupalNode) => {
    return {
    params: {
    // Choose `path` to match the `[path]` routing value
    path: article.path.alias.split('/')[2]
    },
    props: {
    title: article.title,
    body: article.body,
    date: new Date(article.created).toLocaleDateString('en-EN', {
    day: "numeric",
    month: "long",
    year: "numeric"
    })
    }
    }
    });
    }
    ---

    params 内部的属性必须与动态路由的名称匹配。由于文件名是 [path].astro,因此传递给 params 的属性名称必须是 path

    在我们的示例中,props 对象向页面传递三个属性

    • title:一个字符串,表示你帖子的标题。
    • body:一个字符串,表示你条目的内容。
    • date:一个时间戳,基于你文件的创建日期。
  3. 使用页面的 props 来显示你的博客文章。

    src/pages/articles/[path].astro
    ---
    import {Jsona} from "jsona";
    import {DrupalJsonApiParams} from "drupal-jsonapi-params";
    import type {TJsonApiBody} from "jsona/lib/JsonaTypes";
    import type { DrupalNode } from "../../types";
    import {getArticles} from "../../api/drupal";
    // Get all published articles.
    export async function getStaticPaths() {
    const articles = await getArticles();
    return articles.map((article: DrupalNode) => {
    return {
    params: {
    path: article.path.alias.split('/')[2]
    },
    props: {
    title: article.title,
    body: article.body,
    date: new Date(article.created).toLocaleDateString('en-EN', {
    day: "numeric",
    month: "long",
    year: "numeric"
    })
    }
    }
    });
    }
    const {title, date, body} = Astro.props;
    ---
    <html lang="en">
    <head>
    <title>{title}</title>
    </head>
    <body>
    <h1>{title}</h1>
    <time>{date}</time>
    <article set:html={body.value} />
    </body>
    </html>
  4. 导航到你的开发服务器预览,然后点击你的一篇帖子,以确保你的动态路由正常工作。

要部署你的网站,请访问我们的部署指南并按照你偏好的托管提供商的说明进行操作。

更多 CMS 指南

贡献 社区 赞助