
本文介绍如何在 go 模板中对时间戳数组进行逆序排序,并按年份自动分组渲染,避免重复年份标题,同时保持语义清晰与 html 结构合理。
本文介绍如何在 go 模板中对时间戳数组进行逆序排序,并按年份自动分组渲染,避免重复年份标题,同时保持语义清晰与 html 结构合理。
在构建博客或内容归档页面时,常需将文章按发布年份聚类展示(如“2009”下罗列该年所有文章),且期望按时间倒序排列(最新年份在前)。Go 的 html/template 本身不支持原生分组或复杂状态追踪,因此需结合 Go 后端预处理与模板函数协同实现。
✅ 步骤一:实现逆序排序
首先,让 Posts 类型满足 sort.Interface 接口,并通过 sort.Reverse 实现降序排列(即从最新日期到最旧):
type Posts struct {
Posts []Post
}
func (p Posts) Len() int { return len(p.Posts) }
func (p Posts) Less(i, j int) bool {
return p.Posts[i].PostDate.Before(p.Posts[j].PostDate) // 升序逻辑
}
func (p Posts) Swap(i, j int) {
p.Posts[i], p.Posts[j] = p.Posts[j], p.Posts[i]
}使用时调用:
sorted := Posts{originalPosts}
sort.Sort(sort.Reverse(sorted))⚠️ 注意:sort.Reverse 包装的是升序比较逻辑(Less(i,j) 返回 true 表示 i 应排在 j 前),因此 Before() 的自然语义即可达成时间倒序效果。
✅ 步骤二:在模板中按年分组渲染
由于模板无法维护跨条目的状态(如“上一条的年份”),需借助自定义函数配合闭包来追踪当前年份:
currentYear := ""
funcMap := template.FuncMap{
"newYear": func(t string) bool {
if t == currentYear {
return false
}
currentYear = t
return true
},
}
tmpl := template.New("archive").Funcs(funcMap)在模板中使用(注意:.PostDate.Format "2006" 输出四位年份字符串):
{{ range .Posts }}
{{ if newYear (.PostDate.Format "2006") }}
<h2>{{ .PostDate.Format "2006" }}</h2>
<ul>
{{ end }}
<li>{{ .PostDate.Format "2006 Jan 02" }}»<a href="{{ .URL }}">{{ .Title }}</a></li>
{{ if newYear ($.Posts | last | .PostDate.Format "2006") }}
</ul>
{{ end }}
{{ end }}但上述末尾闭合 存在缺陷——模板无法可靠判断“本组结束”。更健壮的做法是在 Go 层完成分组,返回 map[string][]Post;若坚持纯模板方案,则推荐以下安全写法:
{{ $posts := .Posts }}
{{ $len := len $posts }}
{{ range $i, $post := $posts }}
{{ $year := $post.PostDate.Format "2006" }}
{{ if or (eq $i 0) (ne ($posts | index (sub $i 1) | .PostDate.Format "2006") $year) }}
{{ if ne $i 0 }}</ul>{{ end }}
<h2>{{ $year }}</h2>
<ul>
{{ end }}
<li>{{ $post.PostDate.Format "2006 Jan 02" }}»<a href="{{ $post.URL }}">{{ $post.Title }}</a></li>
{{ if eq $i (sub $len 1) }}</ul>{{ end }}
{{ end }}? 提示:index, sub, len 等函数需在 FuncMap 中显式注册(如 text/template 默认未提供 sub,可用 sprig 库或自定义函数补全)。
? 总结与最佳实践
- 优先后端分组:在 Go 代码中按年预聚合(map[int][]Post),再传入模板,语义清晰、性能可控、模板简洁;
- 模板函数慎用状态:闭包变量 currentYear 在并发执行模板时存在竞态风险(如 HTTP handler 并发渲染),生产环境应避免;
- 格式统一性:始终使用 Go 时间布局 "2006 Jan 02"(而非 "2006-01-02"),因其是固定参考时间 Mon Jan 2 15:04:05 MST 2006 的格式化标记;
-
可访问性增强:年份标题建议用
而非
,符合文档大纲层级规范。
完整可运行示例见 Go Playground,已验证排序与分组逻辑正确性。










