diff --git a/actions/common/comments.go b/actions/common/comments.go new file mode 100644 index 0000000..680419d --- /dev/null +++ b/actions/common/comments.go @@ -0,0 +1,64 @@ +package common + +import ( + "context" + "github/fthvgb1/wp-go/helper" + "github/fthvgb1/wp-go/logs" + "github/fthvgb1/wp-go/models" + "strconv" + "time" +) + +func RecentComments(ctx context.Context, n int) (r []models.WpComments) { + r, err := recentCommentsCaches.GetCache(ctx, time.Second) + if len(r) > n { + r = r[0:n] + } + logs.ErrPrintln(err, "get recent comment") + return +} +func recentComments(...any) (r []models.WpComments, err error) { + return models.Find[models.WpComments](models.SqlBuilder{ + {"comment_approved", "1"}, + {"post_status", "publish"}, + }, "comment_ID,comment_author,comment_post_ID,post_title", "", models.SqlBuilder{{"comment_date_gmt", "desc"}}, models.SqlBuilder{ + {"a", "left join", "wp_posts b", "a.comment_post_ID=b.ID"}, + }, 10) +} + +func PostComments(ctx context.Context, Id uint64) ([]models.WpComments, error) { + return postCommentCaches.GetCache(ctx, Id, time.Second, Id) +} + +func postComments(args ...any) ([]models.WpComments, error) { + postId := args[0].(uint64) + return models.Find[models.WpComments](models.SqlBuilder{ + {"comment_approved", "1"}, + {"comment_post_ID", "=", strconv.FormatUint(postId, 10), "int"}, + }, "*", "", models.SqlBuilder{ + {"comment_date_gmt", "asc"}, + {"comment_ID", "asc"}, + }, nil, 0) +} + +func GetCommentById(ctx context.Context, id uint64) (models.WpComments, error) { + return commentsCache.GetCache(ctx, id, time.Second, id) +} + +func GetCommentByIds(ctx context.Context, ids []uint64) ([]models.WpComments, error) { + return commentsCache.GetCacheBatch(ctx, ids, time.Second, ids) +} + +func getCommentByIds(args ...any) (map[uint64]models.WpComments, error) { + ids := args[0].([]uint64) + m := make(map[uint64]models.WpComments) + r, err := models.Find[models.WpComments](models.SqlBuilder{ + {"comment_ID", "in", ""}, {"comment_approved", "1"}, + }, "*", "", nil, nil, 0, helper.SliceMap(ids, helper.ToAny[uint64])) + if err != nil { + return m, err + } + return helper.SliceToMap(r, func(t models.WpComments) uint64 { + return t.CommentId + }), err +} diff --git a/actions/common/common.go b/actions/common/common.go index 3a13073..ba6f2ed 100644 --- a/actions/common/common.go +++ b/actions/common/common.go @@ -2,14 +2,11 @@ package common import ( "context" - "database/sql" "fmt" "github/fthvgb1/wp-go/cache" - "github/fthvgb1/wp-go/helper" "github/fthvgb1/wp-go/logs" "github/fthvgb1/wp-go/models" "github/fthvgb1/wp-go/vars" - "strconv" "sync" "time" ) @@ -27,6 +24,7 @@ var searchPostIdsCache *cache.MapCache[string, PostIds] var maxPostIdCache *cache.SliceCache[uint64] var TotalRaw int var usersCache *cache.MapCache[uint64, models.WpUsers] +var commentsCache *cache.MapCache[uint64, models.WpComments] func InitActionsCommonCache() { archivesCaches = &Arch{ @@ -43,7 +41,6 @@ func InitActionsCommonCache() { postContextCache = cache.NewMapCacheByFn[uint64, PostContext](getPostContext, vars.Conf.ContextPostCacheTime) postsCache = cache.NewMapCacheByBatchFn[uint64, models.WpPosts](getPostsByIds, vars.Conf.PostDataCacheTime) - postsCache.SetCacheFunc(getPostById) categoryCaches = cache.NewSliceCache[models.WpTermsMy](categories, vars.Conf.CategoryCacheTime) @@ -56,7 +53,8 @@ func InitActionsCommonCache() { maxPostIdCache = cache.NewSliceCache[uint64](getMaxPostId, vars.Conf.MaxPostIdCacheTime) usersCache = cache.NewMapCacheByBatchFn[uint64, models.WpUsers](getUsers, vars.Conf.UserInfoCacheTime) - usersCache.SetCacheFunc(getUser) + + commentsCache = cache.NewMapCacheByBatchFn[uint64, models.WpComments](getCommentByIds, time.Hour) } func ClearCache() { @@ -69,40 +67,6 @@ func ClearCache() { usersCache.ClearExpired() } -func GetMonthPostIds(ctx context.Context, year, month string, page, limit int, order string) (r []models.WpPosts, total int, err error) { - res, err := monthPostsCache.GetCache(ctx, fmt.Sprintf("%s%s", year, month), time.Second, year, month) - if err != nil { - return - } - if order == "desc" { - res = helper.SliceReverse(res) - } - total = len(res) - rr := helper.SlicePagination(res, page, limit) - r, err = GetPostsByIds(ctx, rr) - return -} - -func monthPost(args ...any) (r []uint64, err error) { - year, month := args[0].(string), args[1].(string) - where := models.SqlBuilder{ - {"post_type", "in", ""}, - {"post_status", "in", ""}, - {"year(post_date)", year}, - {"month(post_date)", month}, - } - postType := []any{"post"} - status := []any{"publish"} - ids, err := models.Find[models.WpPosts](where, "ID", "", models.SqlBuilder{{"Id", "asc"}}, nil, 0, postType, status) - if err != nil { - return - } - for _, post := range ids { - r = append(r, post.Id) - } - return -} - type PostIds struct { Ids []uint64 Length int @@ -137,79 +101,6 @@ type PostContext struct { next models.WpPosts } -func PostComments(ctx context.Context, Id uint64) ([]models.WpComments, error) { - return postCommentCaches.GetCache(ctx, Id, time.Second, Id) -} - -func postComments(args ...any) ([]models.WpComments, error) { - postId := args[0].(uint64) - return models.Find[models.WpComments](models.SqlBuilder{ - {"comment_approved", "1"}, - {"comment_post_ID", "=", strconv.FormatUint(postId, 10), "int"}, - }, "*", "", models.SqlBuilder{ - {"comment_date_gmt", "asc"}, - {"comment_ID", "asc"}, - }, nil, 0) -} - -func RecentComments(ctx context.Context, n int) (r []models.WpComments) { - r, err := recentCommentsCaches.GetCache(ctx, time.Second) - if len(r) > n { - r = r[0:n] - } - logs.ErrPrintln(err, "get recent comment") - return -} -func recentComments(...any) (r []models.WpComments, err error) { - return models.Find[models.WpComments](models.SqlBuilder{ - {"comment_approved", "1"}, - {"post_status", "publish"}, - }, "comment_ID,comment_author,comment_post_ID,post_title", "", models.SqlBuilder{{"comment_date_gmt", "desc"}}, models.SqlBuilder{ - {"a", "left join", "wp_posts b", "a.comment_post_ID=b.ID"}, - }, 10) -} - -func GetContextPost(ctx context.Context, id uint64, date time.Time) (prev, next models.WpPosts, err error) { - postCtx, err := postContextCache.GetCache(ctx, id, time.Second, date) - if err != nil { - return models.WpPosts{}, models.WpPosts{}, err - } - prev = postCtx.prev - next = postCtx.next - return -} - -func getPostContext(arg ...any) (r PostContext, err error) { - t := arg[0].(time.Time) - next, err := models.FirstOne[models.WpPosts](models.SqlBuilder{ - {"post_date", ">", t.Format("2006-01-02 15:04:05")}, - {"post_status", "in", ""}, - {"post_type", "post"}, - }, "ID,post_title,post_password", nil, []any{"publish"}) - if err == sql.ErrNoRows { - err = nil - } - if err != nil { - return - } - prev, err := models.FirstOne[models.WpPosts](models.SqlBuilder{ - {"post_date", "<", t.Format("2006-01-02 15:04:05")}, - {"post_status", "in", ""}, - {"post_type", "post"}, - }, "ID,post_title", models.SqlBuilder{{"post_date", "desc"}}, []any{"publish"}) - if err == sql.ErrNoRows { - err = nil - } - if err != nil { - return - } - r = PostContext{ - prev: prev, - next: next, - } - return -} - func archives() ([]models.PostArchive, error) { return models.Find[models.PostArchive](models.SqlBuilder{ {"post_type", "post"}, {"post_status", "publish"}, @@ -247,26 +138,6 @@ func categories(...any) (terms []models.WpTermsMy, err error) { return } -func RecentPosts(ctx context.Context, n int) (r []models.WpPosts) { - r, err := recentPostsCaches.GetCache(ctx, time.Second) - if n < len(r) { - r = r[:n] - } - logs.ErrPrintln(err, "get recent post") - return -} -func recentPosts(...any) (r []models.WpPosts, err error) { - r, err = models.Find[models.WpPosts](models.SqlBuilder{{ - "post_type", "post", - }, {"post_status", "publish"}}, "ID,post_title,post_password", "", models.SqlBuilder{{"post_date", "desc"}}, nil, 10) - for i, post := range r { - if post.PostPassword != "" { - PasswordProjectTitle(&r[i]) - } - } - return -} - func PasswordProjectTitle(post *models.WpPosts) { if post.PostPassword != "" { post.PostTitle = fmt.Sprintf("密码保护:%s", post.PostTitle) diff --git a/actions/common/posts.go b/actions/common/posts.go index dbec9b9..988bad3 100644 --- a/actions/common/posts.go +++ b/actions/common/posts.go @@ -2,16 +2,17 @@ package common import ( "context" + "database/sql" "fmt" "github.com/gin-gonic/gin" "github/fthvgb1/wp-go/helper" + "github/fthvgb1/wp-go/logs" "github/fthvgb1/wp-go/models" "strings" "time" ) func GetPostAndCache(ctx context.Context, id uint64) (models.WpPosts, error) { - return postsCache.GetCache(ctx, id, time.Second, id) } @@ -33,16 +34,6 @@ func SearchPost(ctx context.Context, key string, args ...any) (r []models.WpPost return } -func getPostById(id ...any) (post models.WpPosts, err error) { - Id := id[0].(uint64) - posts, err := getPostsByIds([]uint64{Id}) - if err != nil { - return models.WpPosts{}, err - } - post = posts[Id] - return -} - func getPostsByIds(ids ...any) (m map[uint64]models.WpPosts, err error) { m = make(map[uint64]models.WpPosts) id := ids[0].([]uint64) @@ -134,3 +125,98 @@ func GetMaxPostId(ctx *gin.Context) (uint64, error) { Id, err := maxPostIdCache.GetCache(ctx, time.Second) return Id[0], err } + +func RecentPosts(ctx context.Context, n int) (r []models.WpPosts) { + r, err := recentPostsCaches.GetCache(ctx, time.Second) + if n < len(r) { + r = r[:n] + } + logs.ErrPrintln(err, "get recent post") + return +} +func recentPosts(...any) (r []models.WpPosts, err error) { + r, err = models.Find[models.WpPosts](models.SqlBuilder{{ + "post_type", "post", + }, {"post_status", "publish"}}, "ID,post_title,post_password", "", models.SqlBuilder{{"post_date", "desc"}}, nil, 10) + for i, post := range r { + if post.PostPassword != "" { + PasswordProjectTitle(&r[i]) + } + } + return +} + +func GetContextPost(ctx context.Context, id uint64, date time.Time) (prev, next models.WpPosts, err error) { + postCtx, err := postContextCache.GetCache(ctx, id, time.Second, date) + if err != nil { + return models.WpPosts{}, models.WpPosts{}, err + } + prev = postCtx.prev + next = postCtx.next + return +} + +func getPostContext(arg ...any) (r PostContext, err error) { + t := arg[0].(time.Time) + next, err := models.FirstOne[models.WpPosts](models.SqlBuilder{ + {"post_date", ">", t.Format("2006-01-02 15:04:05")}, + {"post_status", "in", ""}, + {"post_type", "post"}, + }, "ID,post_title,post_password", nil, []any{"publish"}) + if err == sql.ErrNoRows { + err = nil + } + if err != nil { + return + } + prev, err := models.FirstOne[models.WpPosts](models.SqlBuilder{ + {"post_date", "<", t.Format("2006-01-02 15:04:05")}, + {"post_status", "in", ""}, + {"post_type", "post"}, + }, "ID,post_title", models.SqlBuilder{{"post_date", "desc"}}, []any{"publish"}) + if err == sql.ErrNoRows { + err = nil + } + if err != nil { + return + } + r = PostContext{ + prev: prev, + next: next, + } + return +} + +func GetMonthPostIds(ctx context.Context, year, month string, page, limit int, order string) (r []models.WpPosts, total int, err error) { + res, err := monthPostsCache.GetCache(ctx, fmt.Sprintf("%s%s", year, month), time.Second, year, month) + if err != nil { + return + } + if order == "desc" { + res = helper.SliceReverse(res) + } + total = len(res) + rr := helper.SlicePagination(res, page, limit) + r, err = GetPostsByIds(ctx, rr) + return +} + +func monthPost(args ...any) (r []uint64, err error) { + year, month := args[0].(string), args[1].(string) + where := models.SqlBuilder{ + {"post_type", "in", ""}, + {"post_status", "in", ""}, + {"year(post_date)", year}, + {"month(post_date)", month}, + } + postType := []any{"post"} + status := []any{"publish"} + ids, err := models.Find[models.WpPosts](where, "ID", "", models.SqlBuilder{{"Id", "asc"}}, nil, 0, postType, status) + if err != nil { + return + } + for _, post := range ids { + r = append(r, post.Id) + } + return +} diff --git a/actions/common/users.go b/actions/common/users.go index 6f6fc73..a07dc6a 100644 --- a/actions/common/users.go +++ b/actions/common/users.go @@ -16,11 +16,6 @@ func getUsers(...any) (m map[uint64]models.WpUsers, err error) { return } -func getUser(a ...any) (r models.WpUsers, err error) { - id := a[0].(uint64) - return models.FindOneById[models.WpUsers](id) -} - func GetUser(ctx *gin.Context, uid uint64) models.WpUsers { r, err := usersCache.GetCache(ctx, uid, time.Second, uid) logs.ErrPrintln(err, "get user", uid) diff --git a/actions/feed.go b/actions/feed.go index 6a70397..39fca9f 100644 --- a/actions/feed.go +++ b/actions/feed.go @@ -16,10 +16,11 @@ import ( "time" ) -var feedCache = cache.NewSliceCache[string](feed, time.Hour) +var feedCache = cache.NewSliceCache(feed, time.Hour) var postFeedCache = cache.NewMapCacheByFn[string, string](postFeed, time.Hour) var tmp = "Mon, 02 Jan 2006 15:04:05 GMT" var templateRss rss2.Rss2 +var commentsFeedCache = cache.NewSliceCache(commentsFeed, time.Hour) func InitFeed() { templateRss = rss2.Rss2{ @@ -85,7 +86,7 @@ func feed(arg ...any) (xml []string, err error) { if t.PostPassword != "" { common.PasswdProjectContent(&t) } else { - desc = plugins.DigestRaw(t.PostContent, 55, t.Id) + desc = plugins.DigestRaw(t.PostContent, 55, fmt.Sprintf("/p/%d", t.Id)) } l := "" if t.CommentStatus == "open" && t.CommentCount > 0 { @@ -201,3 +202,56 @@ func postFeed(arg ...any) (x string, err error) { x = rs.GetXML() return } + +func CommentsFeed(c *gin.Context) { + if !isCacheExpired(c, commentsFeedCache.SetTime()) { + c.Status(http.StatusNotModified) + } else { + r, err := commentsFeedCache.GetCache(c, time.Second, c) + if err != nil { + c.Status(http.StatusInternalServerError) + c.Abort() + c.Error(err) + return + } + setFeed(r[0], c, commentsFeedCache.SetTime()) + } +} + +func commentsFeed(args ...any) (r []string, err error) { + c := args[0].(*gin.Context) + commens := common.RecentComments(c, 10) + rs := templateRss + rs.LastBuildDate = time.Now().Format(time.RFC1123Z) + rs.AtomLink = fmt.Sprintf("%s/comments/feed", models.Options["siteurl"]) + com, err := common.GetCommentByIds(c, helper.SliceMap(commens, func(t models.WpComments) uint64 { + return t.CommentId + })) + if nil != err { + return []string{}, err + } + rs.Items = helper.SliceMap(com, func(t models.WpComments) rss2.Item { + post, _ := common.GetPostAndCache(c, t.CommentPostId) + common.PasswordProjectTitle(&post) + desc := "评论受保护:要查看请输入密码。" + content := t.CommentContent + if post.PostPassword != "" { + common.PasswdProjectContent(&post) + content = post.PostContent + } else { + desc = plugins.DigestRaw(t.CommentContent, 55, fmt.Sprintf("%s/p/%d#comment-%d", models.Options["siteurl"], post.Id, t.CommentId)) + content = t.CommentContent + } + return rss2.Item{ + Title: fmt.Sprintf("《%s》的评论", post.PostTitle), + Link: fmt.Sprintf("%s/p/%d#comment-%d", models.Options["siteurl"], post.Id, t.CommentId), + Creator: t.CommentAuthor, + Description: desc, + PubDate: t.CommentDateGmt.Format(time.RFC1123Z), + Guid: fmt.Sprintf("%s#commment-%d", post.Guid, t.CommentId), + Content: content, + } + }) + r = []string{rs.GetXML()} + return +} diff --git a/cache/map.go b/cache/map.go index afab49f..2e43191 100644 --- a/cache/map.go +++ b/cache/map.go @@ -9,11 +9,11 @@ import ( ) type MapCache[K comparable, V any] struct { - data map[K]mapCacheStruct[V] - mutex *sync.Mutex - setCacheFunc func(...any) (V, error) - setBatchCacheFn func(...any) (map[K]V, error) - expireTime time.Duration + data map[K]mapCacheStruct[V] + mutex *sync.Mutex + cacheFunc func(...any) (V, error) + batchCacheFn func(...any) (map[K]V, error) + expireTime time.Duration } func NewMapCache[K comparable, V any](expireTime time.Duration) *MapCache[K, V] { @@ -27,7 +27,7 @@ type mapCacheStruct[T any] struct { } func (m *MapCache[K, V]) SetCacheFunc(fn func(...any) (V, error)) { - m.setCacheFunc = fn + m.cacheFunc = fn } func (m *MapCache[K, V]) GetSetTime(k K) (t time.Time) { @@ -39,24 +39,41 @@ func (m *MapCache[K, V]) GetSetTime(k K) (t time.Time) { } func (m *MapCache[K, V]) SetCacheBatchFunc(fn func(...any) (map[K]V, error)) { - m.setBatchCacheFn = fn + m.batchCacheFn = fn + if m.cacheFunc == nil { + m.setCacheFn(fn) + } } -func NewMapCacheByFn[K comparable, V any](fun func(...any) (V, error), expireTime time.Duration) *MapCache[K, V] { +func (m *MapCache[K, V]) setCacheFn(fn func(...any) (map[K]V, error)) { + m.cacheFunc = func(a ...any) (V, error) { + id := a[0].(K) + r, err := fn([]K{id}) + if err != nil { + var rr V + return rr, err + } + return r[id], err + } +} + +func NewMapCacheByFn[K comparable, V any](fn func(...any) (V, error), expireTime time.Duration) *MapCache[K, V] { return &MapCache[K, V]{ - mutex: &sync.Mutex{}, - setCacheFunc: fun, - expireTime: expireTime, - data: make(map[K]mapCacheStruct[V]), + mutex: &sync.Mutex{}, + cacheFunc: fn, + expireTime: expireTime, + data: make(map[K]mapCacheStruct[V]), } } func NewMapCacheByBatchFn[K comparable, V any](fn func(...any) (map[K]V, error), expireTime time.Duration) *MapCache[K, V] { - return &MapCache[K, V]{ - mutex: &sync.Mutex{}, - setBatchCacheFn: fn, - expireTime: expireTime, - data: make(map[K]mapCacheStruct[V]), + r := &MapCache[K, V]{ + mutex: &sync.Mutex{}, + batchCacheFn: fn, + expireTime: expireTime, + data: make(map[K]mapCacheStruct[V]), } + r.setCacheFn(fn) + return r } func (m *MapCache[K, V]) Flush() { @@ -79,7 +96,7 @@ func (m *MapCache[K, V]) SetByBatchFn(params ...any) error { m.mutex.Lock() defer m.mutex.Unlock() - r, err := m.setBatchCacheFn(params...) + r, err := m.batchCacheFn(params...) if err != nil { return err } @@ -122,7 +139,7 @@ func (m *MapCache[K, V]) GetCache(c context.Context, key K, timeout time.Duratio if data.incr > t { return } - r, er := m.setCacheFunc(params...) + r, er := m.cacheFunc(params...) if err != nil { err = er return @@ -185,7 +202,7 @@ func (m *MapCache[K, V]) GetCacheBatch(c context.Context, key []K, timeout time. if tt > t { return } - r, er := m.setBatchCacheFn(params...) + r, er := m.batchCacheFn(params...) if err != nil { err = er return diff --git a/helper/func.go b/helper/func.go index 99d0d9d..284e103 100644 --- a/helper/func.go +++ b/helper/func.go @@ -230,3 +230,12 @@ func SliceSelfReverse[T any](arr []T) []T { } return arr } + +func SliceToMap[K comparable, V any](arr []V, fn func(V) K) map[K]V { + m := make(map[K]V) + for _, v := range arr { + k := fn(v) + m[k] = v + } + return m +} diff --git a/helper/func_test.go b/helper/func_test.go index 063ca15..1e3766b 100644 --- a/helper/func_test.go +++ b/helper/func_test.go @@ -505,3 +505,37 @@ func TestSliceSelfReverse(t *testing.T) { }) } } + +func TestSliceToMap(t *testing.T) { + type ss struct { + id int + v string + } + type args struct { + arr []ss + fn func(ss) int + } + tests := []struct { + name string + args args + want map[int]ss + }{ + { + name: "t1", + args: args{ + arr: []ss{{1, "k1"}, {2, "v2"}}, + fn: func(s ss) int { + return s.id + }, + }, + want: map[int]ss{1: {1, "k1"}, 2: {2, "v2"}}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := SliceToMap(tt.args.arr, tt.args.fn); !reflect.DeepEqual(got, tt.want) { + t.Errorf("SliceToMap() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/plugins/digest.go b/plugins/digest.go index 5b87f94..6183099 100644 --- a/plugins/digest.go +++ b/plugins/digest.go @@ -35,7 +35,7 @@ func digestRaw(arg ...any) (string, error) { } else if limit == 0 { return "", nil } - return DigestRaw(str, limit, id), nil + return DigestRaw(str, limit, fmt.Sprintf("/p/%d", id)), nil } func DigestCache(ctx *gin.Context, id uint64, str string) string { @@ -43,7 +43,7 @@ func DigestCache(ctx *gin.Context, id uint64, str string) string { return content } -func DigestRaw(str string, limit int, id uint64) string { +func DigestRaw(str string, limit int, u string) string { if r := more.FindString(str); r != "" { m := strings.Split(str, r) str = m[0] @@ -98,11 +98,11 @@ func DigestRaw(str string, limit int, id uint64) string { content = string(ru[:i]) closeTag := helper.CloseHtmlTag(content) - tmp := `%s......%s

继续阅读

` + tmp := `%s......%s

继续阅读

` if strings.Contains(closeTag, "pre") || strings.Contains(closeTag, "code") { - tmp = `%s%s......

继续阅读

` + tmp = `%s%s......

继续阅读

` } - content = fmt.Sprintf(tmp, content, closeTag, id) + content = fmt.Sprintf(tmp, content, closeTag, u) } return content diff --git a/route/route.go b/route/route.go index 0152453..0e572e7 100644 --- a/route/route.go +++ b/route/route.go @@ -57,6 +57,7 @@ func SetupRouter() *gin.Engine { r.GET("/p/:id", actions.Detail) r.GET("/p/:id/feed", actions.PostFeed) r.GET("/feed", actions.Feed) + r.GET("/comments/feed", actions.CommentsFeed) if helper.IsContainInArr(gin.Mode(), []string{gin.DebugMode, gin.TestMode}) { pprof.Register(r, "dev/pprof") }