跳到主要内容

数据模板

数据模板让你可以在 Hugo 中使用外部数据源,包括本地数据文件和远程 API。这使得 Hugo 不仅能生成静态内容,还能展示动态数据。

数据源类型

Hugo 支持以下数据源:

  • 本地数据文件:存放在 data/ 目录的文件
  • 全局资源:存放在 assets/ 目录的文件
  • 页面资源:页面目录中的数据文件
  • 远程资源:从网络获取的数据

支持的格式:

  • JSON
  • TOML
  • YAML
  • CSV
  • XML

本地数据文件

data 目录

data/ 目录是存放本地数据文件的默认位置:

data/
├── authors.json
├── products.toml
├── links.yaml
└── menus/
└── main.json

JSON 格式

data/authors.json

{
"zhangsan": {
"name": "张三",
"email": "[email protected]",
"bio": "前端开发工程师",
"social": {
"github": "zhangsan",
"twitter": "zhangsan_dev"
}
},
"lisi": {
"name": "李四",
"email": "[email protected]",
"bio": "后端开发工程师",
"social": {
"github": "lisi"
}
}
}

TOML 格式

data/products.toml

[[products]]
id = 1
name = "Hugo 主题"
price = 49.99
features = ["响应式", "SEO 优化", "多语言"]

[[products]]
id = 2
name = "Hugo 插件"
price = 19.99
features = ["评论系统", "搜索功能"]

YAML 格式

data/links.yaml

- name: Hugo 官网
url: https://gohugo.io
description: Hugo 静态网站生成器官方文档

- name: Hugo 主题库
url: https://themes.gohugo.io
description: Hugo 官方主题库

在模板中使用

通过 .Site.Data 访问数据:

<!-- 访问 authors.json -->
<h2>作者列表</h2>
<ul>
{{ range $key, $author := .Site.Data.authors }}
<li>
<strong>{{ $author.name }}</strong> - {{ $author.bio }}
<a href="https://github.com/{{ $author.social.github }}">GitHub</a>
</li>
{{ end }}
</ul>

<!-- 访问 products.toml -->
<h2>产品列表</h2>
{{ range .Site.Data.products.products }}
<div class="product">
<h3>{{ .name }}</h3>
<p>价格:¥{{ .price }}</p>
<ul>
{{ range .features }}
<li>{{ . }}</li>
{{ end }}
</ul>
</div>
{{ end }}

嵌套数据

目录结构对应嵌套的数据结构:

data/
└── menus/
└── main.json

访问方式:

{{ range .Site.Data.menus.main }}
<a href="{{ .url }}">{{ .name }}</a>
{{ end }}

全局资源数据

将数据文件放在 assets/ 目录,使用 resources.Gettransform.Unmarshal 处理:

assets/data/books.json

[
{"title": "Go 语言编程", "author": "许式伟"},
{"title": "深入理解计算机系统", "author": "Randal E. Bryant"}
]

在模板中使用:

{{ $data := resources.Get "data/books.json" | transform.Unmarshal }}

<h2>推荐书籍</h2>
<ul>
{{ range $data }}
<li>{{ .title }} - {{ .author }}</li>
{{ end }}
</ul>

CSV 数据

CSV 数据处理

assets/data/pets.csv

name,type,breed,age
Spot,dog,Collie,3
Felix,cat,Malicious,7
Max,dog,Labrador,5

在模板中解析 CSV:

{{ $csv := resources.Get "data/pets.csv" | transform.Unmarshal }}

<table>
<thead>
<tr>
{{ range index $csv 0 }}
<th>{{ . }}</th>
{{ end }}
</tr>
</thead>
<tbody>
{{ range after 1 $csv }}
<tr>
{{ range . }}
<td>{{ . }}</td>
{{ end }}
</tr>
{{ end }}
</tbody>
</table>

CSV 短代码示例

创建一个通用的 CSV 表格短代码:

layouts/shortcodes/csv-table.html

{{ with $file := .Get 0 }}
{{ with resources.Get $file }}
{{ with . | transform.Unmarshal }}
<table class="csv-table">
<thead>
<tr>
{{ range index . 0 }}
<th>{{ . }}</th>
{{ end }}
</tr>
</thead>
<tbody>
{{ range after 1 . }}
<tr>
{{ range . }}
<td>{{ . }}</td>
{{ end }}
</tr>
{{ end }}
</tbody>
</table>
{{ end }}
{{ else }}
{{ errorf "找不到文件:%s" $file }}
{{ end }}
{{ else }}
{{ errorf "需要指定 CSV 文件路径" }}
{{ end }}

使用:

{{< csv-table "data/pets.csv" >}}

远程数据

获取远程 JSON

{{ $url := "https://api.github.com/users/gohugoio/repos" }}
{{ $data := resources.GetRemote $url | transform.Unmarshal }}

<h2>Hugo 官方仓库</h2>
<ul>
{{ range first 10 $data }}
<li>
<a href="{{ .html_url }}">{{ .name }}</a>
<span>{{ .stargazers_count }} stars</span>
</li>
{{ end }}
</ul>

处理错误

远程请求可能失败,需要处理错误:

{{ $url := "https://api.example.com/data.json" }}
{{ with resources.GetRemote $url }}
{{ with .Err }}
{{ errorf "获取远程数据失败:%s" . }}
{{ else }}
{{ $data := . | transform.Unmarshal }}
{{ range $data }}
<p>{{ .title }}</p>
{{ end }}
{{ end }}
{{ else }}
{{ errorf "无法访问远程资源:%s" $url }}
{{ end }}

带请求头

某些 API 需要认证:

{{ $url := "https://api.example.com/data" }}
{{ $headers := dict "Authorization" "Bearer your-token" }}
{{ $data := resources.GetRemote $url (dict "headers" $headers) | transform.Unmarshal }}

缓存远程数据

远程数据会被缓存,可以配置缓存时间:

[caches]
[caches.getjson]
dir = ':cacheDir/:project'
maxAge = '12h'
[caches.getcsv]
dir = ':cacheDir/:project'
maxAge = '12h'

页面资源数据

数据文件可以作为页面资源存放:

content/posts/my-post/
├── index.md
└── data.json

访问页面资源数据:

{{ with .Resources.Get "data.json" }}
{{ $data := . | transform.Unmarshal }}
{{ range $data }}
<p>{{ . }}</p>
{{ end }}
{{ end }}

实际应用示例

网站链接导航

data/links.toml

[[tools]]
name = "Hugo"
url = "https://gohugo.io"
description = "最快的静态网站生成器"
category = "static-site"

[[tools]]
name = "Netlify"
url = "https://netlify.com"
description = "现代化的托管平台"
category = "hosting"

[[tools]]
name = "VS Code"
url = "https://code.visualstudio.com"
description = "流行的代码编辑器"
category = "editor"

模板:

<h2>推荐工具</h2>
{{ $categories := dict
"static-site" "静态网站"
"hosting" "托管服务"
"editor" "编辑器"
}}

{{ range $cat, $name := $categories }}
<h3>{{ $name }}</h3>
<ul>
{{ range where $.Site.Data.links.tools "category" $cat }}
<li>
<a href="{{ .url }}" target="_blank">{{ .name }}</a>
<p>{{ .description }}</p>
</li>
{{ end }}
</ul>
{{ end }}

作者信息

data/authors.toml

[default]
name = "管理员"
avatar = "/images/default-avatar.jpg"
bio = "网站管理员"

[zhangsan]
name = "张三"
avatar = "/images/zhangsan.jpg"
bio = "前端开发工程师,专注于 React 和 Vue"
social = { github = "zhangsan", twitter = "zhangsan_dev" }

[lisi]
name = "李四"
avatar = "/images/lisi.jpg"
bio = "后端开发工程师,专注于 Go 和 Rust"
social = { github = "lisi" }

在文章中引用作者:

---
title: "我的文章"
author: "zhangsan"
---

模板中获取作者信息:

{{ $authorKey := .Params.author | default "default" }}
{{ $author := index .Site.Data.authors $authorKey }}

<div class="author-card">
<img src="{{ $author.avatar }}" alt="{{ $author.name }}">
<div>
<h4>{{ $author.name }}</h4>
<p>{{ $author.bio }}</p>
{{ with $author.social }}
<div class="social-links">
{{ with .github }}
<a href="https://github.com/{{ . }}">GitHub</a>
{{ end }}
{{ with .twitter }}
<a href="https://twitter.com/{{ . }}">Twitter</a>
{{ end }}
</div>
{{ end }}
</div>
</div>

产品目录

data/products.json

{
"categories": [
{"id": "software", "name": "软件"},
{"id": "hardware", "name": "硬件"}
],
"items": [
{
"id": "hugo-theme",
"name": "Hugo 高级主题",
"category": "software",
"price": 49.99,
"features": ["响应式设计", "SEO 优化", "多语言支持"],
"image": "/images/themes/advanced.png"
},
{
"id": "dev-keyboard",
"name": "开发者键盘",
"category": "hardware",
"price": 199.99,
"features": ["机械轴体", "RGB 灯光", "可编程按键"],
"image": "/images/keyboards/dev.png"
}
]
}

模板:

{{ $products := .Site.Data.products }}

{{ range $products.categories }}
<h2>{{ .name }}</h2>
<div class="product-grid">
{{ range where $products.items "category" .id }}
<div class="product-card">
<img src="{{ .image }}" alt="{{ .name }}">
<h3>{{ .name }}</h3>
<p class="price">¥{{ .price }}</p>
<ul>
{{ range .features }}
<li>{{ . }}</li>
{{ end }}
</ul>
</div>
{{ end }}
</div>
{{ end }}

API 数据展示

从 GitHub API 获取项目信息:

{{ $api := "https://api.github.com/repos/gohugoio/hugo" }}
{{ with resources.GetRemote $api }}
{{ with .Err }}
<p>无法获取数据</p>
{{ else }}
{{ $repo := . | transform.Unmarshal }}
<div class="github-card">
<h2>{{ $repo.name }}</h2>
<p>{{ $repo.description }}</p>
<div class="stats">
<span>⭐ {{ $repo.stargazers_count }}</span>
<span>🍴 {{ $repo.forks_count }}</span>
<span>👁️ {{ $repo.watchers_count }}</span>
</div>
<a href="{{ $repo.html_url }}">访问仓库</a>
</div>
{{ end }}
{{ end }}

数据查询

where 函数

使用 where 函数过滤数据:

<!-- 筛选特定分类 -->
{{ range where .Site.Data.products.items "category" "software" }}
<p>{{ .name }}</p>
{{ end }}

<!-- 筛选价格大于 50 -->
{{ range where .Site.Data.products.items "price" "gt" 50 }}
<p>{{ .name }}: ¥{{ .price }}</p>
{{ end }}

比较操作符:

操作符说明
eq等于
ne不等于
gt大于
ge大于等于
lt小于
le小于等于
in包含在列表中
not in不包含在列表中

切片和排序

<!-- 取前 N 个 -->
{{ range first 5 .Site.Data.links.tools }}
<p>{{ .name }}</p>
{{ end }}

<!-- 排序后取前 N 个 -->
{{ range first 5 (sort .Site.Data.products.items "price" "desc") }}
<p>{{ .name }}: ¥{{ .price }}</p>
{{ end }}

数据合并

Hugo 会合并主题和项目的数据:

项目/
├── data/
│ └── config.toml # 项目数据(优先)
└── themes/
└── mytheme/
└── data/
└── config.toml # 主题数据

项目数据会覆盖主题中的同名数据。

最佳实践

1. 命名空间

为主题或模块的数据使用命名空间:

data/
└── mytheme/
├── config.toml
└── defaults.toml

2. 合理的数据结构

根据使用场景选择合适的数据结构:

# 列表型数据适合数组
[[items]]
name = "item1"

# 键值查找适合 Map
[users.user1]
name = "张三"

3. 错误处理

始终处理可能的错误:

{{ with .Site.Data.authors }}
{{ range . }}
<!-- 处理数据 -->
{{ end }}
{{ else }}
<p>暂无数据</p>
{{ end }}

4. 缓存管理

对于远程数据,合理设置缓存时间:

[caches]
[caches.getjson]
maxAge = '6h' # 6 小时缓存

OpenAPI 3 支持

Hugo 提供了内置的 OpenAPI 3 支持,可以解析和处理 OpenAPI(原名 Swagger)规范文件。这对于生成 API 文档非常有用。

什么是 OpenAPI?

OpenAPI 是一个用于描述 RESTful API 的标准规范。通过 OpenAPI 规范文件,可以:

  • 描述 API 的端点、请求参数和响应格式
  • 生成 API 文档
  • 进行 API 测试
  • 生成客户端代码

获取 OpenAPI 规范

从本地或远程获取 OpenAPI 规范文件:

<!-- 本地文件 -->
{{ $spec := resources.Get "api/openapi.yaml" | openapi3.Unmarshal }}

<!-- 远程文件 -->
{{ $spec := resources.GetRemote "https://api.example.com/openapi.json" | openapi3.Unmarshal }}

访问 OpenAPI 数据

解析后的 OpenAPI 规范是一个结构化的对象:

{{ $spec := resources.Get "api/openapi.yaml" | openapi3.Unmarshal }}

<!-- 基本信息 -->
<h1>{{ $spec.Info.Title }}</h1>
<p>版本: {{ $spec.Info.Version }}</p>
<p>{{ $spec.Info.Description }}</p>

<!-- API 端点列表 -->
<h2>端点列表</h2>
{{ range $path, $pathItem := $spec.Paths }}
<div class="endpoint">
<h3>{{ $path }}</h3>
{{ range $method, $operation := $pathItem }}
{{ if and (ne $method "Ref") (ne $method "Summary") (ne $method "Description") }}
<div class="method method-{{ $method }}">
<span class="verb">{{ upper $method }}</span>
{{ with $operation.Summary }}
<span class="summary">{{ . }}</span>
{{ end }}
</div>
{{ end }}
{{ end }}
</div>
{{ end }}

生成 API 文档页面

创建一个完整的 API 文档页面:

{{ define "main" }}
{{ $spec := resources.Get "api/openapi.yaml" | openapi3.Unmarshal }}

<div class="api-docs">
<header class="api-header">
<h1>{{ $spec.Info.Title }}</h1>
<p class="version">版本 {{ $spec.Info.Version }}</p>
{{ with $spec.Info.Description }}
<div class="description">{{ . | markdownify }}</div>
{{ end }}
{{ with $spec.Info.Contact }}
<p class="contact">
联系方式:
{{ with .Email }}<a href="mailto:{{ . }}">{{ . }}</a>{{ end }}
{{ with .URL }}<a href="{{ . }}">{{ . }}</a>{{ end }}
</p>
{{ end }}
</header>

<section class="api-servers">
<h2>服务器地址</h2>
<ul>
{{ range $spec.Servers }}
<li>
<code>{{ .URL }}</code>
{{ with .Description }} - {{ . }}{{ end }}
</li>
{{ end }}
</ul>
</section>

<section class="api-endpoints">
<h2>API 端点</h2>
{{ range $path, $pathItem := $spec.Paths }}
<div class="endpoint-group">
<h3>{{ $path }}</h3>

{{ range $method, $operation := $pathItem }}
{{ if and (ne $method "Ref") (ne $method "Summary") (ne $method "Description") }}
<div class="operation">
<div class="operation-header">
<span class="method-badge method-{{ $method }}">
{{ upper $method }}
</span>
<span class="operation-summary">{{ $operation.Summary }}</span>
</div>

{{ with $operation.Description }}
<div class="operation-description">{{ . | markdownify }}</div>
{{ end }}

{{ with $operation.Parameters }}
<div class="parameters">
<h4>参数</h4>
<table>
<thead>
<tr>
<th>名称</th>
<th>位置</th>
<th>类型</th>
<th>必需</th>
<th>描述</th>
</tr>
</thead>
<tbody>
{{ range . }}
<tr>
<td><code>{{ .Name }}</code></td>
<td>{{ .In }}</td>
<td>{{ .Schema.Type }}</td>
<td>{{ if .Required }}是{{ else }}否{{ end }}</td>
<td>{{ .Description }}</td>
</tr>
{{ end }}
</tbody>
</table>
</div>
{{ end }}

{{ with $operation.RequestBody }}
<div class="request-body">
<h4>请求体</h4>
{{ with .Description }}<p>{{ . }}</p>{{ end }}
{{ range $contentType, $mediaType := .Content }}
<p>Content-Type: <code>{{ $contentType }}</code></p>
{{ with $mediaType.Schema }}
<pre><code>{{ . | jsonify (dict "indent" " ") }}</code></pre>
{{ end }}
{{ end }}
</div>
{{ end }}

{{ with $operation.Responses }}
<div class="responses">
<h4>响应</h4>
{{ range $status, $response := . }}
<div class="response response-{{ $status }}">
<h5>状态码: {{ $status }}</h5>
{{ with $response.Description }}<p>{{ . }}</p>{{ end }}
</div>
{{ end }}
</div>
{{ end }}
</div>
{{ end }}
{{ end }}
</div>
{{ end }}
</section>
</div>
{{ end }}

OpenAPI 规范示例

assets/api/openapi.yaml

openapi: 3.0.0
info:
title: 示例 API
version: 1.0.0
description: 这是一个示例 API 文档
contact:
name: API 支持
email: [email protected]
url: https://example.com/support

servers:
- url: https://api.example.com/v1
description: 生产环境
- url: https://staging-api.example.com/v1
description: 测试环境

paths:
/users:
get:
summary: 获取用户列表
description: 返回所有用户的列表
parameters:
- name: limit
in: query
description: 返回用户数量限制
required: false
schema:
type: integer
minimum: 1
maximum: 100
- name: offset
in: query
description: 分页偏移量
required: false
schema:
type: integer
minimum: 0
responses:
'200':
description: 成功返回用户列表
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/User'
post:
summary: 创建新用户
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/NewUser'
responses:
'201':
description: 用户创建成功
content:
application/json:
schema:
$ref: '#/components/schemas/User'

components:
schemas:
User:
type: object
properties:
id:
type: integer
example: 1
name:
type: string
example: 张三
email:
type: string
format: email
example: [email protected]
NewUser:
type: object
required:
- name
- email
properties:
name:
type: string
email:
type: string
format: email

在短代码中使用

创建一个显示单个 API 端点的短代码:

layouts/shortcodes/api-endpoint.html

{{ $spec := resources.Get "api/openapi.yaml" | openapi3.Unmarshal }}
{{ $path := .Get "path" }}
{{ $method := .Get "method" | lower }}

{{ with index $spec.Paths $path }}
{{ with index . $method }}
<div class="api-endpoint">
<div class="endpoint-header">
<span class="method method-{{ $method }}">{{ upper $method }}</span>
<code>{{ $path }}</code>
</div>
<p class="summary">{{ .Summary }}</p>
{{ with .Description }}
<div class="description">{{ . | markdownify }}</div>
{{ end }}
</div>
{{ end }}
{{ end }}

使用:

{{< api-endpoint path="/users" method="GET" >}}

小结

数据模板让 Hugo 能够:

  • 从本地文件加载结构化数据
  • 从远程 API 获取动态数据
  • 使用 CSV、JSON、TOML、YAML 等格式
  • 在模板中灵活处理和展示数据

结合 Hugo 的模板功能,数据模板可以构建功能丰富的静态网站,如产品目录、团队介绍、项目展示等。