Files
pure-note/internal/httpapi/feed.go
T
wangairnan 6e83426ca9 HTTP 层与单二进制入口:路由 handler、会话/CSRF/限流防线、SPA 嵌入与 meta 注入
- internal/httpapi:§7.1 全部路由(公开浏览 / 管理端 / 认证 / feed),
  服务端统一可见性过滤(含回收站仅 admin 出口)、图片魔数校验与
  immutable/no-store 缓存头分流、统一 404 防枚举、slug 自解冲突与
  409 字段级错误、fail-only 登录限流(429 + Retry-After)、
  设置白名单(永不序列化口令哈希)
- internal/webui:go:embed dist + SPA fallback(资产指纹长缓存、
  深链回退 index.html)+ html/template 元信息注入(仅可见笔记)
- cmd/pure-note:serve/init/backup/gc/version 子命令,优雅停机与
  每小时会话清理
- 含全部 §13 测试组:表驱动可见性矩阵、迁移守卫、认证会话、CSRF、
  上传、回收站/gc、slug 策略、设置白名单、webui MapFS 单测
2026-09-08 08:14:23 +08:00

130 lines
3.6 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,
})
}
feed := rssFeed{
Version: "2.0",
Channel: rssChannel{
Title: ss.SiteTitle,
Link: base + "/",
Description: ss.SiteDesc,
Language: "zh-CN",
LastBuild: lastBuild.Format(time.RFC1123Z),
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"))
}