Files
pure-note/internal/httpapi/feed.go
T
wangairnan 5457aeaa33 feat: 新增发布日期自选、站点 Logo、回收站清空等十项改进
- 编辑页标题改 filled 变体;标签改 tags 选择器(可勾选既有/输入新建)
- 笔记发布日期可自选(schema v2 新增 published_at 并回填),前台展示发布日期
- 登录页移除「请输入管理密码以继续」「连续失败将被暂时锁定」文案
- 站点设置每页条数收敛为 10/20/30/50 选择器,后端白名单同源校验
- 站点设置两卡片宽屏左右/窄屏上下;修改密码按钮改常规大小
- 后台各页顶栏固定高度,切换页面不再抖动
- 站点 Logo:settings 新增 site_logo(站内路径),gc/孤儿清单豁免 Logo 图片,
  博客 header 站点名前展示,设置页支持上传/更换/清除
- 回收站右上角一键清空(DELETE /api/admin/trash,含确认弹窗)
- pn init 写入 Markdown 语法示例文档(slug welcome,公开)与程序生成的示例图片
2026-09-09 11:10:30 +08:00

135 lines
3.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package httpapi
import (
"encoding/xml"
"net/http"
"time"
"pure-note/internal/markdown"
)
// ---- RSS 2.0(§7.1:仅公开;服务端 goldmark+bluemonday 渲染)----
type rssFeed struct {
XMLName xml.Name `xml:"rss"`
Version string `xml:"version,attr"`
Channel rssChannel `xml:"channel"`
}
type rssChannel struct {
Title string `xml:"title"`
Link string `xml:"link"`
Description string `xml:"description"`
Language string `xml:"language,omitempty"`
LastBuild string `xml:"lastBuildDate,omitempty"`
Items []rssItem `xml:"item"`
}
type rssItem struct {
Title string `xml:"title"`
Link string `xml:"link"`
GUID string `xml:"guid"`
PubDate string `xml:"pubDate"` // RFC 822(RSS 2.0 规范)
Description string `xml:"description"` // 已清洗的 HTML(经 encoding/xml 自动转义)
}
// feedItemLimit RSS 条目上限(个人规模足够,避免全量渲染)。
const feedItemLimit = 50
func (s *Server) handleRSS(w http.ResponseWriter, r *http.Request) {
ss, err := s.st.GetSiteSettings()
if err != nil {
writeError(w, http.StatusInternalServerError, "internal", "内部错误")
return
}
base := baseURL(r)
notes, err := s.st.ListPublicNotesFull(feedItemLimit)
if err != nil {
writeError(w, http.StatusInternalServerError, "internal", "内部错误")
return
}
items := make([]rssItem, 0, len(notes))
var lastBuild time.Time
for _, n := range notes {
// 服务端渲染:goldmark(默认转义)→ bluemonday 白名单(§7.5)
html := markdown.Render(n.Content)
pub := time.Unix(n.UpdatedAt, 0)
if pub.After(lastBuild) {
lastBuild = pub
}
items = append(items, rssItem{
Title: n.Title,
Link: base + "/notes/" + n.Slug,
GUID: base + "/notes/" + n.Slug,
PubDate: pub.Format(time.RFC1123Z),
Description: html,
})
}
// 无公开笔记时省略 lastBuildDate(零值 Format 会产出公元 1 年的非法日期)
lastBuildStr := ""
if !lastBuild.IsZero() {
lastBuildStr = lastBuild.Format(time.RFC1123Z)
}
feed := rssFeed{
Version: "2.0",
Channel: rssChannel{
Title: ss.SiteTitle,
Link: base + "/",
Description: ss.SiteDesc,
Language: "zh-CN",
LastBuild: lastBuildStr,
Items: items,
},
}
w.Header().Set("Content-Type", "application/rss+xml; charset=utf-8")
w.Header().Set("Cache-Control", "public, max-age=300")
w.Write([]byte(xml.Header))
enc := xml.NewEncoder(w)
enc.Indent("", " ")
_ = enc.Encode(feed)
}
// ---- sitemap(仅公开笔记)----
type urlSet struct {
XMLName xml.Name `xml:"urlset"`
XMLNS string `xml:"xmlns,attr"`
URLs []siteURL `xml:"url"`
}
type siteURL struct {
Loc string `xml:"loc"`
LastMod time.Time `xml:"lastmod,omitempty"`
}
func (s *Server) handleSitemap(w http.ResponseWriter, r *http.Request) {
base := baseURL(r)
set := urlSet{XMLNS: "http://www.sitemaps.org/schemas/sitemap/0.9"}
set.URLs = append(set.URLs, siteURL{Loc: base + "/"})
notes, _, err := s.st.ListPublicNotes(1, 10000, "")
if err != nil {
writeError(w, http.StatusInternalServerError, "internal", "内部错误")
return
}
for _, n := range notes {
set.URLs = append(set.URLs, siteURL{
Loc: base + "/notes/" + n.Slug,
LastMod: time.Unix(n.UpdatedAt, 0),
})
}
w.Header().Set("Content-Type", "application/xml; charset=utf-8")
w.Header().Set("Cache-Control", "public, max-age=3600")
w.Write([]byte(xml.Header))
enc := xml.NewEncoder(w)
enc.Indent("", " ")
_ = enc.Encode(set)
}
// ---- robots.txt ----
func (s *Server) handleRobots(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.Header().Set("Cache-Control", "public, max-age=86400")
w.Write([]byte("User-agent: *\nAllow: /\nDisallow: /admin\n\nSitemap: " + baseURL(r) + "/sitemap.xml\n"))
}