数据模板
数据模板让你可以在 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.Get 和 transform.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 的模板功能,数据模板可以构建功能丰富的静态网站,如产品目录、团队介绍、项目展示等。