diff --git a/actions/feed.go b/actions/feed.go
index 73c0e45..484d3bb 100644
--- a/actions/feed.go
+++ b/actions/feed.go
@@ -1,90 +1,114 @@
package actions
import (
- "bytes"
"fmt"
"github.com/gin-gonic/gin"
"github/fthvgb1/wp-go/actions/common"
+ "github/fthvgb1/wp-go/cache"
"github/fthvgb1/wp-go/helper"
- "github/fthvgb1/wp-go/logs"
"github/fthvgb1/wp-go/models"
"github/fthvgb1/wp-go/plugins"
- "github/fthvgb1/wp-go/templates"
- "html"
- "html/template"
+ "github/fthvgb1/wp-go/rss2"
"net/http"
"strings"
"time"
- "unicode/utf8"
)
-func Feed() func(ctx *gin.Context) {
- fs, err := template.ParseFS(templates.TemplateFs, "feed/feed.gohtml")
- if err != nil {
- panic(err)
+var feedCache = cache.NewSliceCache[string](feed, time.Hour)
+var tmp = "Mon, 02 Jan 2006 15:04:05 GMT"
+
+func FeedCached(c *gin.Context) {
+ if !isCacheExpired(c, feedCache.SetTime()) {
+ c.Status(http.StatusNotModified)
+ c.Abort()
+ return
}
- return func(c *gin.Context) {
- c.Header("Content-Type", "application/rss+xml; charset=UTF-8")
- c.Header("Cache-Control", "no-cache, must-revalidate, max-age=0")
- c.Header("Expires", "Wed, 11 Jan 1984 05:00:00 GMT")
- //c.Header("Last-Modified", "false")
- c.Header("ETag", helper.StringMd5("gmt"))
- r := common.RecentPosts(c, 10)
- ids := helper.SliceMap(r, func(t models.WpPosts) uint64 {
- return t.Id
- })
- posts, err := common.GetPostsByIds(c, ids)
- if err != nil {
- c.Status(http.StatusInternalServerError)
- c.Abort()
- return
- }
- type p struct {
- models.WpPosts
- Cates string
- CommentLink string
- Username string
- Category string
- Link string
- Description string
- Date string
- }
- rr := helper.SliceMap(posts, func(t models.WpPosts) p {
- common.PasswordProjectTitle(&t)
- if t.PostPassword != "" {
- common.PasswdProjectContent(&t)
- }
- l := ""
- if t.CommentStatus == "open" || t.CommentCount > 0 {
- l = fmt.Sprintf("%s/p/%d#comments", models.Options["siteurl"], t.Id)
- }
- user := common.GetUser(c, t.PostAuthor)
- content := plugins.DigestRaw(t.PostContent, utf8.RuneCountInString(t.PostContent), t.Id)
- t.PostContent = content
- return p{
- WpPosts: t,
- Cates: strings.Join(t.Categories, "、"),
- CommentLink: l,
- Username: user.DisplayName,
- Link: fmt.Sprintf("%s/p/%d", models.Options["siteurl"], t.Id),
- Description: plugins.DigestRaw(content, 55, t.Id),
- Date: t.PostDateGmt.Format(time.RFC1123Z),
- }
- })
- h := gin.H{
- "posts": rr,
- "options": models.Options,
- "now": time.Now().Format(time.RFC1123Z),
- }
-
- var buf bytes.Buffer
- err = fs.Execute(&buf, h)
- if err != nil {
- logs.ErrPrintln(err, "parse template")
- return
- }
-
- c.String(http.StatusOK, html.UnescapeString(buf.String()))
- }
-
+ c.Next()
+}
+
+func isCacheExpired(c *gin.Context, lastTime time.Time) bool {
+ eTag := helper.StringMd5(lastTime.Format(tmp))
+ since := c.Request.Header.Get("If-Modified-Since")
+ cTag := c.Request.Header.Get("If-None-Match")
+ if since != "" && cTag != "" {
+ cGMT, err := time.Parse(tmp, since)
+ if err == nil && lastTime.Unix() <= cGMT.Unix() && eTag == cTag {
+ c.Status(http.StatusNotModified)
+ return false
+ }
+ }
+ return true
+}
+
+func Feed(c *gin.Context) {
+ s, err := feedCache.GetCache(c, time.Second, c)
+ if err != nil {
+ c.Status(http.StatusInternalServerError)
+ c.Abort()
+ c.Error(err)
+ return
+ }
+ lastTimeGMT := feedCache.SetTime().Format(tmp)
+ eTag := helper.StringMd5(lastTimeGMT)
+ c.Header("Content-Type", "application/rss+xml; charset=UTF-8")
+ c.Header("Last-Modified", lastTimeGMT)
+ c.Header("ETag", eTag)
+ c.String(http.StatusOK, s[0])
+}
+
+func feed(arg ...any) (xml []string, err error) {
+ c := arg[0].(*gin.Context)
+ r := common.RecentPosts(c, 10)
+ ids := helper.SliceMap(r, func(t models.WpPosts) uint64 {
+ return t.Id
+ })
+ posts, err := common.GetPostsByIds(c, ids)
+ if err != nil {
+ return
+ }
+ rs := rss2.Rss2{
+ Title: models.Options["blogname"],
+ AtomLink: fmt.Sprintf("%s/feed", models.Options["home"]),
+ Link: models.Options["siteurl"],
+ Description: models.Options["blogdescription"],
+ LastBuildDate: time.Now().Format(time.RFC1123Z),
+ Language: "zh-CN",
+ UpdatePeriod: "hourly",
+ UpdateFrequency: 1,
+ Generator: models.Options["home"],
+ Items: nil,
+ }
+
+ rs.Items = helper.SliceMap(posts, func(t models.WpPosts) rss2.Item {
+ desc := "无法提供摘要。这是一篇受保护的文章。"
+ common.PasswordProjectTitle(&t)
+ if t.PostPassword != "" {
+ common.PasswdProjectContent(&t)
+ } else {
+ desc = plugins.DigestRaw(t.PostContent, 55, t.Id)
+ }
+ l := ""
+ if t.CommentStatus == "open" && t.CommentCount > 0 {
+ l = fmt.Sprintf("%s/p/%d#comments", models.Options["siteurl"], t.Id)
+ } else if t.CommentStatus == "open" && t.CommentCount == 0 {
+ l = fmt.Sprintf("%s/p/%d#respond", models.Options["siteurl"], t.Id)
+ }
+ user := common.GetUser(c, t.PostAuthor)
+
+ return rss2.Item{
+ Title: t.PostTitle,
+ Creator: user.DisplayName,
+ Guid: t.Guid,
+ SlashComments: int(t.CommentCount),
+ Content: t.PostContent,
+ Category: strings.Join(t.Categories, "、"),
+ CommentLink: l,
+ CommentRss: fmt.Sprintf("%s/p/%d/feed", models.Options["siteurl"], t.Id),
+ Link: fmt.Sprintf("%s/p/%d", models.Options["siteurl"], t.Id),
+ Description: desc,
+ PubDate: t.PostDateGmt.Format(time.RFC1123Z),
+ }
+ })
+ xml = []string{rs.GetXML()}
+ return
}
diff --git a/cache/slice.go b/cache/slice.go
index d53a607..16fcc46 100644
--- a/cache/slice.go
+++ b/cache/slice.go
@@ -17,6 +17,10 @@ type SliceCache[T any] struct {
incr int
}
+func (c *SliceCache[T]) SetTime() time.Time {
+ return c.setTime
+}
+
func NewSliceCache[T any](fun func(...any) ([]T, error), duration time.Duration) *SliceCache[T] {
return &SliceCache[T]{
mutex: &sync.Mutex{},
diff --git a/route/route.go b/route/route.go
index fe7afc9..371b0b4 100644
--- a/route/route.go
+++ b/route/route.go
@@ -52,7 +52,7 @@ func SetupRouter() *gin.Engine {
r.GET("/p/date/:year/:month/page/:page", actions.Index)
r.POST("/login", actions.Login)
r.GET("/p/:id", actions.Detail)
- r.GET("/feed", actions.Feed())
+ r.GET("/feed", actions.FeedCached, actions.Feed)
if helper.IsContainInArr(gin.Mode(), []string{gin.DebugMode, gin.TestMode}) {
pprof.Register(r, "dev/pprof")
}
diff --git a/rss2/rss2.go b/rss2/rss2.go
new file mode 100644
index 0000000..26ad662
--- /dev/null
+++ b/rss2/rss2.go
@@ -0,0 +1,142 @@
+package rss2
+
+import (
+ "fmt"
+ "github/fthvgb1/wp-go/helper"
+ "strings"
+)
+
+var template = `
+
+
+
+ {$title}
+
+ {$link}
+ {$description}
+ {$lastBuildDate}
+ {$lang}
+
+ {$updatePeriod}
+
+
+ {$updateFrequency}
+
+ {$generator}
+ {$items}
+
+
+
+`
+var templateItems = `
+ -
+ {$title}
+ {$link}
+ {$comments}
+
+ {$pubDate}
+
+ {$guid}
+
+
+ {$commentRss}
+ {$commentNumber}
+
+
+`
+
+type Rss2 struct {
+ Title string
+ AtomLink string
+ Link string
+ Description string
+ LastBuildDate string
+ Language string
+ UpdatePeriod string
+ UpdateFrequency int
+ Generator string
+ Items []Item
+}
+
+type Item struct {
+ Title string
+ Link string
+ CommentLink string
+ Creator string
+ PubDate string
+ Category string
+ Guid string
+ Description string
+ Content string
+ CommentRss string
+ SlashComments int
+}
+
+func (r Rss2) GetXML() (xml string) {
+ xml = template
+ for k, v := range map[string]string{
+ "{$title}": r.Title,
+ "{$link}": r.Link,
+ "{$feedLink}": r.AtomLink,
+ "{$description}": r.Description,
+ "{$lastBuildDate}": r.LastBuildDate,
+ "{$lang}": r.Language,
+ "{$updatePeriod}": r.UpdatePeriod,
+ "{$updateFrequency}": fmt.Sprintf("%d", r.UpdateFrequency),
+ "{$generator}": r.Generator,
+ "{$items}": strings.Join(helper.SliceMap(r.Items, func(t Item) string {
+ return t.GetXml()
+ }), ""),
+ } {
+ xml = strings.Replace(xml, k, v, -1)
+ }
+ return
+}
+
+func (i Item) GetXml() (xml string) {
+ xml = templateItems
+ for k, v := range map[string]string{
+ "{$title}": i.Title,
+ "{$link}": i.Link,
+ "{$comments}": i.GetComments(),
+ "{$author}": i.Creator,
+ "{$pubDate}": i.PubDate,
+ "{$category}": i.Category,
+ "{$guid}": i.Guid,
+ "{$description}": i.Description,
+ "{$content}": i.Content,
+ "{$commentRss}": i.getCommentRss(),
+ "{$commentNumber}": i.getSlashComments(),
+ } {
+ xml = strings.Replace(xml, k, v, -1)
+ }
+ return
+}
+
+func (i Item) GetComments() string {
+ r := ""
+ if i.CommentLink != "" {
+ r = fmt.Sprintf("%s", i.CommentLink)
+ }
+ return r
+}
+
+func (i Item) getCommentRss() (r string) {
+ if i.CommentLink != "" && i.SlashComments > 0 {
+ r = fmt.Sprintf("%s", i.CommentRss)
+ }
+ return
+}
+func (i Item) getSlashComments() (r string) {
+ if i.SlashComments > 0 {
+ r = fmt.Sprintf("%d", i.SlashComments)
+ }
+ return
+}