Compare commits

..

2 Commits

Author SHA1 Message Date
088fc306de fix bug,improve pagination cache and revise comment render 2023-12-20 22:30:55 +08:00
57d345e445 fix var memory cache bug 2023-12-17 19:34:04 +08:00
23 changed files with 690 additions and 113 deletions

View File

@ -134,9 +134,14 @@ func PostComment(c *gin.Context) {
err = er
return
}
cache.NewCommentCache().Set(c, up.RawQuery, string(s))
uuu := ""
uu, _ := url.Parse(res.Header.Get("Location"))
uuu := str.Join(uu.Path, "?", uu.RawQuery)
if up.RawQuery != "" {
cache.NewCommentCache().Set(c, up.RawQuery, string(s))
uuu = str.Join(uu.Path, "?", uu.RawQuery)
} else {
uuu = str.Join(uu.Path)
}
c.Redirect(http.StatusFound, uuu)
return
}

View File

@ -8,6 +8,7 @@ import (
"github.com/fthvgb1/wp-go/app/pkg/models"
"github.com/fthvgb1/wp-go/cache"
"github.com/fthvgb1/wp-go/cache/cachemanager"
"github.com/fthvgb1/wp-go/helper"
"github.com/fthvgb1/wp-go/helper/slice"
"github.com/fthvgb1/wp-go/safety"
"time"
@ -52,15 +53,30 @@ func InitActionsCommonCache() {
return config.GetConfig().CacheTime.RecentCommentsCacheTime
})
cachemanager.NewMemoryMapCache(nil, dao.PostComments, c.CacheTime.PostCommentsCacheTime,
"postCommentIds",
func() time.Duration {
return config.GetConfig().CacheTime.PostCommentsCacheTime
},
cache.NewIncreaseUpdate("comment-increaseUpdate", dao.GetIncreaseComment, 30*time.Second,
func() time.Duration {
return config.GetConfig().CacheTime.CommentsIncreaseUpdateTime
}))
cachemanager.NewMemoryMapCache(nil, dao.CommentNum, 30*time.Second, "commentNumber", func() time.Duration {
return config.GetConfig().CacheTime.CommentsIncreaseUpdateTime
})
cachemanager.NewPaginationCache(
cachemanager.NewMemoryMapCache[string, helper.PaginationData[uint64]](nil, nil, 30*time.Second,
"PostCommentsIds", func() time.Duration {
return config.GetConfig().CacheTime.PostCommentsCacheTime
},
cache.NewIncreaseUpdate("PostCommentsIds-increaseUpdate", CommentDataIncreaseUpdate, 30*time.Second,
func() time.Duration {
return config.GetConfig().CacheTime.CommentsIncreaseUpdateTime
}),
),
1000, dao.PostCommentsIds, dao.PostCommentLocal, nil, nil, 300, "PostCommentsIds")
cachemanager.NewMemoryMapCache(dao.CommentDates, nil, time.Hour, "postCommentData", func() time.Duration {
return config.GetConfig().CacheTime.CommentsCacheTime
})
cachemanager.NewMemoryMapCache[uint64, []models.Comments](nil, CommentDataIncreaseUpdates, time.Hour, func() time.Duration {
return config.GetConfig().CacheTime.CommentsCacheTime
}, "increaseComment30s", cache.NewIncreaseUpdate("increaseComment30s", IncreaseUpdates, 30*time.Second, nil))
cachemanager.NewVarMemoryCache(dao.GetMaxPostId, c.CacheTime.MaxPostIdCacheTime, "maxPostId", func() time.Duration {
return config.GetConfig().CacheTime.MaxPostIdCacheTime
@ -74,10 +90,6 @@ func InitActionsCommonCache() {
return config.GetConfig().CacheTime.UserInfoCacheTime
})
cachemanager.NewMemoryMapCache(dao.GetCommentByIds, nil, c.CacheTime.CommentsCacheTime, "commentData", func() time.Duration {
return config.GetConfig().CacheTime.CommentsCacheTime
})
cachemanager.NewVarMemoryCache(dao.AllUsername, c.CacheTime.UserInfoCacheTime, "allUsername", func() time.Duration {
return config.GetConfig().CacheTime.UserInfoCacheTime
})

View File

@ -2,11 +2,16 @@ package cache
import (
"context"
"github.com/fthvgb1/wp-go/app/pkg/dao"
"github.com/fthvgb1/wp-go/app/pkg/logs"
"github.com/fthvgb1/wp-go/app/pkg/models"
"github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/cache"
"github.com/fthvgb1/wp-go/cache/cachemanager"
"github.com/fthvgb1/wp-go/helper"
"github.com/fthvgb1/wp-go/helper/number"
"github.com/fthvgb1/wp-go/helper/slice"
str "github.com/fthvgb1/wp-go/helper/strings"
"time"
)
@ -20,23 +25,104 @@ func RecentComments(ctx context.Context, n int) (r []models.Comments) {
return
}
func PostComments(ctx context.Context, Id uint64) ([]models.Comments, error) {
ids, err := cachemanager.Get[[]uint64]("postCommentIds", ctx, Id, time.Second)
func PostComments(ctx context.Context, Id uint64) ([]models.PostComments, error) {
ids, err := cachemanager.Get[[]uint64]("PostCommentsIds", ctx, Id, time.Second)
if err != nil {
return nil, err
}
return GetCommentByIds(ctx, ids)
return GetCommentDataByIds(ctx, ids)
}
func GetCommentById(ctx context.Context, id uint64) (models.Comments, error) {
return cachemanager.Get[models.Comments]("commentData", ctx, id, time.Second)
func GetCommentById(ctx context.Context, id uint64) (models.PostComments, error) {
return cachemanager.Get[models.PostComments]("postCommentData", ctx, id, time.Second)
}
func GetCommentByIds(ctx context.Context, ids []uint64) ([]models.Comments, error) {
return cachemanager.GetMultiple[models.Comments]("commentData", ctx, ids, time.Second)
func GetCommentDataByIds(ctx context.Context, ids []uint64) ([]models.PostComments, error) {
return cachemanager.GetMultiple[models.PostComments]("postCommentData", ctx, ids, time.Second)
}
func NewCommentCache() *cache.MapCache[string, string] {
r, _ := cachemanager.GetMapCache[string, string]("NewComment")
return r
}
func CommentDataIncreaseUpdates(_ context.Context, _ uint64, _ ...any) ([]models.Comments, error) {
return nil, nil
}
func IncreaseUpdates(ctx context.Context, currentData []models.Comments, postId uint64, t time.Time, _ ...any) ([]models.Comments, bool, bool, error) {
var maxId uint64
if len(currentData) > 0 {
maxId = currentData[len(currentData)-1].CommentId
} else {
maxId, err := dao.LatestCommentId(ctx, postId)
return []models.Comments{{CommentId: maxId}}, true, false, err
}
v, err := dao.IncreaseCommentData(ctx, postId, maxId, t)
if err != nil {
return nil, false, false, err
}
if len(v) < 1 {
return nil, false, true, nil
}
m, err := dao.CommentDates(ctx, v)
if err != nil {
return nil, false, false, err
}
CommentData, _ := cachemanager.GetMapCache[uint64, models.PostComments]("postCommentData")
data := slice.Map(v, func(t uint64) models.Comments {
comments := m[t].Comments
if comments.CommentParent > 0 {
vv, ok := CommentData.Get(ctx, comments.CommentParent)
if ok && !slice.IsContained(vv.Children, comments.CommentId) {
vv.Children = append(vv.Children, comments.CommentId)
CommentData.Set(ctx, comments.CommentParent, vv)
}
}
CommentData.Set(ctx, comments.CommentId, models.PostComments{Comments: comments})
return comments
})
return data, true, false, nil
}
func CommentDataIncreaseUpdate(ctx context.Context, currentData helper.PaginationData[uint64], postId string, _ time.Time, _ ...any) (data helper.PaginationData[uint64], save bool, refresh bool, err error) {
refresh = true
increaseUpdateData, _ := cachemanager.GetMapCache[uint64, []models.Comments]("increaseComment30s")
v, ok := increaseUpdateData.Get(ctx, str.ToInt[uint64](postId))
if !ok {
return
}
if len(v) < 1 {
return
}
if len(currentData.Data) > 0 {
if slice.IsContained(currentData.Data, v[0].CommentId) {
return
}
}
dat := slice.FilterAndMap(v, func(t models.Comments) (uint64, bool) {
if wpconfig.GetOption("thread_comments") != "1" || "1" == wpconfig.GetOption("thread_comments_depth") {
return t.CommentId, t.CommentId > 0
}
return t.CommentId, t.CommentId > 0 && t.CommentParent == 0
})
if len(dat) > 0 {
save = true
refresh = false
var a []uint64
a = append(currentData.Data, dat...)
slice.Sorts(a, wpconfig.GetOption("comment_order"))
data.Data = a
data.TotalRaw = len(data.Data)
}
return data, save, refresh, err
}
func UpdateCommentCache(ctx context.Context, timeout time.Duration, postId uint64) (err error) {
c, _ := cachemanager.GetPaginationCache[uint64, uint64]("PostCommentsIds")
if c.IsSwitchDB(postId) {
return
}
_, err = cachemanager.Get[[]models.Comments]("increaseComment30s", ctx, postId, timeout)
return
}

13
app/pkg/cache/feed.go vendored
View File

@ -106,7 +106,12 @@ func postFeed(c context.Context, id string, _ ...any) (x string, err error) {
if post.Id == 0 || err != nil {
return
}
comments, err := PostComments(c, post.Id)
limit := str.ToInteger(wpconfig.GetOption("comments_per_page"), 10)
ids, _, err := cachemanager.Pagination[uint64]("PostCommentsIds", c, time.Second, ID, 1, limit, "desc")
if err != nil {
return
}
comments, err := cachemanager.GetMultiple[models.PostComments]("postCommentData", c, ids, time.Second)
if err != nil {
return
}
@ -135,7 +140,7 @@ func postFeed(c context.Context, id string, _ ...any) (x string, err error) {
}
}
} else {
rs.Items = slice.Map(comments, func(t models.Comments) rss2.Item {
rs.Items = slice.Map(comments, func(t models.PostComments) rss2.Item {
return rss2.Item{
Title: fmt.Sprintf("评价者:%s", t.CommentAuthor),
Link: fmt.Sprintf("%s/p/%d#comment-%d", site, post.Id, t.CommentId),
@ -158,13 +163,13 @@ func commentsFeed(c context.Context, _ ...any) (r []string, err error) {
rs.LastBuildDate = time.Now().Format(timeFormat)
site := wpconfig.GetOption("siteurl")
rs.AtomLink = fmt.Sprintf("%s/comments/feed", site)
com, err := GetCommentByIds(c, slice.Map(commens, func(t models.Comments) uint64 {
com, err := GetCommentDataByIds(c, slice.Map(commens, func(t models.Comments) uint64 {
return t.CommentId
}))
if nil != err {
return []string{}, err
}
rs.Items = slice.Map(com, func(t models.Comments) rss2.Item {
rs.Items = slice.Map(com, func(t models.PostComments) rss2.Item {
post, _ := GetPostById(c, t.CommentPostId)
desc := "评论受保护:要查看请输入密码。"
content := t.CommentContent

View File

@ -5,9 +5,11 @@ import (
"database/sql"
"errors"
"github.com/fthvgb1/wp-go/app/pkg/models"
"github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/helper"
"github.com/fthvgb1/wp-go/helper/number"
"github.com/fthvgb1/wp-go/helper/slice"
str "github.com/fthvgb1/wp-go/helper/strings"
"github.com/fthvgb1/wp-go/model"
"time"
)
@ -81,12 +83,13 @@ func GetCommentByIds(ctx context.Context, ids []uint64, _ ...any) (map[uint64]mo
return m, nil
}
func GetIncreaseComment(ctx context.Context, currentData []uint64, k uint64, t time.Time, _ ...any) (data []uint64, save bool, refresh bool, err error) {
func GetIncreaseComment(ctx context.Context, currentData []uint64, k uint64, _ time.Time, _ ...any) (data []uint64, save bool, refresh bool, err error) {
r, err := model.ChunkFind[models.Comments](ctx, 1000, model.Conditions(
model.Where(model.SqlBuilder{
{"comment_approved", "1"},
{"comment_post_ID", "=", number.IntToString(k), "int"},
{"comment_date", ">=", t.Format(time.DateTime)},
//{"comment_date", ">=", t.Format(time.DateTime)},
{"comment_ID", ">", number.IntToString(currentData[len(currentData)-1])},
}),
model.Fields("comment_ID"),
model.Order(model.SqlBuilder{
@ -112,3 +115,156 @@ func GetIncreaseComment(ctx context.Context, currentData []uint64, k uint64, t t
save = true
return
}
func CommentDates(ctx context.Context, CommentIds []uint64, _ ...any) (map[uint64]models.PostComments, error) {
if len(CommentIds) < 1 {
return nil, nil
}
m := make(map[uint64]models.PostComments)
off := 0
threadComments := wpconfig.GetOption("thread_comments")
where := model.SqlBuilder{
{"comment_approved", "1"},
}
var in [][]any
if threadComments == "1" {
where = append(where, []string{"and", "comment_ID", "in", "", "", "or", "comment_parent", "in", "", ""})
} else {
where = append(where, []string{"comment_ID", "in", ""})
}
for {
id := slice.Slice(CommentIds, off, 200)
if len(id) < 1 {
break
}
if threadComments == "1" {
in = [][]any{slice.ToAnySlice(id), slice.ToAnySlice(id)}
} else {
in = [][]any{slice.ToAnySlice(id)}
}
r, err := model.Finds[models.Comments](ctx, model.Conditions(
model.Where(where),
model.Fields("*"),
model.In(in...),
))
if err != nil {
return m, err
}
rr := slice.GroupBy(r, func(t models.Comments) (uint64, models.Comments) {
return t.CommentParent, t
})
mm := map[uint64][]uint64{}
for u, comments := range rr {
slice.SimpleSort(comments, slice.ASC, func(t models.Comments) uint64 {
return t.CommentId
})
mm[u] = slice.Map(comments, func(t models.Comments) uint64 {
return t.CommentId
})
}
for _, comments := range r {
var children []uint64
if threadComments == "1" {
children = mm[comments.CommentId]
}
v := models.PostComments{
Comments: comments,
Children: children,
}
m[comments.CommentId] = v
}
off += 200
}
return m, nil
}
func CommentNum(ctx context.Context, postId uint64, _ ...any) (int, error) {
n, err := model.GetField[models.Posts](ctx, "comment_count", model.Conditions(
model.Where(model.SqlBuilder{{"ID", "=", number.IntToString(postId), "int"}})))
if err != nil {
return 0, err
}
return str.ToInteger(n, 0), err
}
func PostCommentLocal(_ context.Context, data []uint64, _ uint64, page, limit int, _ ...any) ([]uint64, int, error) {
/*order := wpconfig.GetOption("comment_order")
if order == "desc" {
r := slice.ReversePagination(data, page, limit)
if len(r) < 1 {
return nil, 0, nil
}
r = slice.SortsNew(r, slice.DESC)
return r, len(data), nil
}*/
return slice.Pagination(data, page, limit), len(data), nil
}
func PostCommentsIds(ctx context.Context, postId uint64, page, limit, totalRaw int, _ ...any) ([]uint64, int, error) {
order := wpconfig.GetOption("comment_order")
threadComments := wpconfig.GetOption("thread_comments")
where := model.SqlBuilder{
{"comment_approved", "1"},
{"comment_post_ID", "=", number.IntToString(postId), "int"},
}
if threadComments == "1" || "1" == wpconfig.GetOption("thread_comments_depth") {
where = append(where, []string{"comment_parent", "0"})
}
r, total, err := model.Pagination[models.Comments](ctx, model.Conditions(
model.Where(where),
model.TotalRaw(totalRaw),
model.Fields("comment_ID"),
model.Order(model.SqlBuilder{
{"comment_date_gmt", order},
{"comment_ID", "asc"},
}),
), page, limit)
if err != nil && errors.Is(err, sql.ErrNoRows) {
err = nil
}
return slice.Map(r, func(t models.Comments) uint64 {
return t.CommentId
}), total, err
}
func IncreaseCommentData(ctx context.Context, postId, maxCommentId uint64, _ time.Time) ([]uint64, error) {
r, err := model.ChunkFind[models.Comments](ctx, 1000, model.Conditions(
model.Where(model.SqlBuilder{
{"comment_approved", "1"},
{"comment_post_ID", "=", number.IntToString(postId), "int"},
{"comment_ID", ">", number.IntToString(maxCommentId), "int"},
}),
model.Fields("comment_ID"),
model.Order(model.SqlBuilder{
{"comment_date_gmt", "asc"},
{"comment_ID", "asc"},
})),
)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
err = nil
return nil, nil
}
return nil, err
}
return slice.Map(r, func(t models.Comments) uint64 {
return t.CommentId
}), err
}
func LatestCommentId(ctx context.Context, postId uint64) (uint64, error) {
v, err := model.GetField[models.Comments](ctx, "comment_ID", model.Conditions(
model.Where(model.SqlBuilder{
{"comment_approved", "1"},
{"comment_post_ID", "=", number.IntToString(postId), "int"},
}),
model.Order(model.SqlBuilder{{"comment_ID", "desc"}}),
model.Limit(1),
))
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
err = nil
}
return 0, err
}
return str.ToInteger[uint64](v, 0), err
}

View File

@ -3,14 +3,17 @@ package db
import (
"context"
"github.com/fthvgb1/wp-go/app/pkg/config"
"github.com/fthvgb1/wp-go/cache/reload"
"github.com/fthvgb1/wp-go/model"
"github.com/fthvgb1/wp-go/safety"
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
"log"
"runtime"
)
var safeDb = safety.NewVar[*sqlx.DB](nil)
var showQuerySql func() bool
func InitDb() (*safety.Var[*sqlx.DB], error) {
c := config.GetConfig()
@ -36,6 +39,9 @@ func InitDb() (*safety.Var[*sqlx.DB], error) {
if preDb != nil {
_ = preDb.Close()
}
showQuerySql = reload.FnVal("showQuerySql", false, func() bool {
return config.GetConfig().ShowQuerySql
})
return safeDb, err
}
@ -44,14 +50,20 @@ func QueryDb(db *safety.Var[*sqlx.DB]) *model.SqlxQuery {
nil,
nil))
model.SetSelect(query, func(ctx context.Context, a any, s string, args ...any) error {
if config.GetConfig().ShowQuerySql {
go log.Println(model.FormatSql(s, args...))
if showQuerySql() {
_, f, l, _ := runtime.Caller(5)
go func() {
log.Printf("%v:%v %v\n", f, l, model.FormatSql(s, args...))
}()
}
return query.Selects(ctx, a, s, args...)
})
model.SetGet(query, func(ctx context.Context, a any, s string, args ...any) error {
if config.GetConfig().ShowQuerySql {
go log.Println(model.FormatSql(s, args...))
if showQuerySql() {
_, f, l, _ := runtime.Caller(5)
go func() {
log.Printf("%v:%v %v\n", f, l, model.FormatSql(s, args...))
}()
}
return query.Gets(ctx, a, s, args...)
})

View File

@ -29,3 +29,8 @@ func (w Comments) PrimaryKey() string {
func (w Comments) Table() string {
return "wp_comments"
}
type PostComments struct {
Comments
Children []uint64
}

View File

@ -1,8 +1,10 @@
package plugins
import (
"context"
"github.com/fthvgb1/wp-go/app/pkg/models"
"github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/helper/number"
"github.com/fthvgb1/wp-go/helper/slice"
str "github.com/fthvgb1/wp-go/helper/strings"
"github.com/gin-gonic/gin"
@ -12,11 +14,12 @@ import (
type CommentHandler struct {
*gin.Context
comments []*Comments
maxDepth int
depth int
isTls bool
i CommentHtml
comments []*Comments
maxDepth int
depth int
isTls bool
i CommentHtml
isThreadComments bool
}
type Comments struct {
@ -25,7 +28,8 @@ type Comments struct {
}
type CommentHtml interface {
FormatLi(c *gin.Context, m models.Comments, depth int, isTls bool, eo, parent string) string
FormatLi(c context.Context, m models.Comments, depth, maxDepth, page int, isTls, isThreadComments bool, eo, parent string) string
FloorOrder(wpOrder string, i, j models.PostComments) bool
}
func FormatComments(c *gin.Context, i CommentHtml, comments []models.Comments, maxDepth int) string {
@ -45,10 +49,10 @@ func FormatComments(c *gin.Context, i CommentHtml, comments []models.Comments, m
isTls: isTls,
i: i,
}
return h.formatComment(h.comments, true)
return h.formatComment(h.comments)
}
func (d CommentHandler) formatComment(comments []*Comments, isTop bool) (html string) {
func (d CommentHandler) formatComment(comments []*Comments) (html string) {
s := str.NewBuilder()
if d.depth >= d.maxDepth {
comments = d.findComments(comments)
@ -71,13 +75,11 @@ func (d CommentHandler) formatComment(comments []*Comments, isTop bool) (html st
parent = "parent"
fl = true
}
s.WriteString(d.i.FormatLi(d.Context, comment.Comments, d.depth, d.isTls, eo, parent))
s.WriteString(d.i.FormatLi(d.Context, comment.Comments, d.depth, d.maxDepth, 1, d.isTls, d.isThreadComments, eo, parent))
if fl {
d.depth++
s.WriteString(`<ol class="children">`, d.formatComment(comment.Children, false), `</ol>`)
if isTop {
d.depth = 1
}
s.WriteString(`<ol class="children">`, d.formatComment(comment.Children), `</ol>`)
d.depth--
}
s.WriteString("</li><!-- #comment-## -->")
}
@ -140,8 +142,24 @@ func CommentRender() CommonCommentFormat {
type CommonCommentFormat struct {
}
func (c CommonCommentFormat) FormatLi(ctx *gin.Context, m models.Comments, depth int, isTls bool, eo, parent string) string {
return FormatLi(CommonLi(), ctx, m, depth, isTls, eo, parent)
func (c CommonCommentFormat) FormatLi(_ context.Context, m models.Comments, currentDepth, maxDepth, page int, isTls, isThreadComments bool, eo, parent string) string {
return FormatLi(CommonLi(), m, currentDepth, maxDepth, page, isTls, isThreadComments, eo, parent)
}
func (c CommonCommentFormat) FloorOrder(wpOrder string, i, j models.PostComments) bool {
return i.CommentId > j.CommentId
}
func respond(m models.Comments, isShow bool) string {
if !isShow {
return ""
}
pId := number.IntToString(m.CommentPostId)
cId := number.IntToString(m.CommentId)
return str.Replace(respondTml, map[string]string{
"{{PostId}}": pId,
"{{CommentId}}": cId,
"{{CommentAuthor}}": m.CommentAuthor,
})
}
var li = `
@ -153,14 +171,11 @@ var li = `
src="{{Gravatar}}"
srcset="{{Gravatar}} 2x"
class="avatar avatar-56 photo" height="56" width="56" loading="lazy">
<b class="fn">
<a href="{{CommentAuthorUrl}}" rel="external nofollow ugc"
class="url">{{CommentAuthor}}</a>
</b>
<b class="fn">{{CommentAuthor}}</b>
<span class="says">说道</span></div><!-- .comment-author -->
<div class="comment-metadata">
<a href="/p/{{PostId}}#comment-{{CommentId}}">
<a href="/p/{{PostId}}/comment-page-{{page}}#comment-{{CommentId}}">
<time datetime="{{CommentDateGmt}}">{{CommentDate}}</time>
</a></div><!-- .comment-metadata -->
@ -170,30 +185,38 @@ var li = `
<p>{{CommentContent}}</p>
</div><!-- .comment-content -->
<div class="reply">
{{respond}}
</article><!-- .comment-body -->
`
var respondTml = `<div class="reply">
<a rel="nofollow" class="comment-reply-link"
href="/p/{{PostId}}?replytocom={{CommentId}}#respond" data-commentid="{{CommentId}}" data-postid="{{PostId}}"
data-belowelement="div-comment-{{CommentId}}" data-respondelement="respond"
data-replyto="回复给{{CommentAuthor}}"
aria-label="回复给{{CommentAuthor}}">回复</a>
</div>
</article><!-- .comment-body -->
</div>`
`
func FormatLi(li string, c *gin.Context, comments models.Comments, depth int, isTls bool, eo, parent string) string {
func FormatLi(li string, comments models.Comments, currentDepth, maxDepth, page int, isTls, isThreadComments bool, eo, parent string) string {
isShow := false
if isThreadComments && currentDepth < maxDepth {
isShow = true
}
for k, v := range map[string]string{
"{{CommentId}}": strconv.FormatUint(comments.CommentId, 10),
"{{Depth}}": strconv.Itoa(depth),
"{{Depth}}": strconv.Itoa(currentDepth),
"{{Gravatar}}": Gravatar(comments.CommentAuthorEmail, isTls),
"{{CommentAuthorUrl}}": comments.CommentAuthorUrl,
"{{CommentAuthor}}": comments.CommentAuthor,
"{{PostId}}": strconv.FormatUint(comments.CommentPostId, 10),
"{{page}}": strconv.Itoa(page),
"{{CommentDateGmt}}": comments.CommentDateGmt.String(),
"{{CommentDate}}": comments.CommentDate.Format("2006-01-02 15:04"),
"{{CommentContent}}": comments.CommentContent,
"{{eo}}": eo,
"{{parent}}": parent,
"{{respond}}": respond(comments, isShow),
} {
li = strings.Replace(li, k, v, -1)
}

View File

@ -2,6 +2,8 @@ package plugins
import (
"fmt"
"github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/helper"
str "github.com/fthvgb1/wp-go/helper/strings"
"regexp"
"strings"
@ -18,6 +20,13 @@ type PageEle struct {
func TwentyFifteenPagination() PageEle {
return twentyFifteen
}
func TwentyFifteenCommentPagination() CommentPageEle {
return twentyFifteenComment
}
type CommentPageEle struct {
PageEle
}
var twentyFifteen = PageEle{
PrevEle: `<a class="prev page-numbers" href="%s">上一页</a>`,
@ -28,6 +37,12 @@ var twentyFifteen = PageEle{
CurrentEle: `<span aria-current="page" class="page-numbers current">
<span class="meta-nav screen-reader-text"> </span>%d</span>`,
}
var twentyFifteenComment = CommentPageEle{
PageEle{
PrevEle: `<div class="nav-previous"><a href="%s#comments">%s</a></div>`,
NextEle: `<div class="nav-next"><a href="%s#comments">%s</a></div>`,
},
}
func (p PageEle) Current(page, totalPage, totalRow int) string {
return fmt.Sprintf(p.CurrentEle, page)
@ -50,6 +65,7 @@ func (p PageEle) Middle(page int, url string) string {
}
var reg = regexp.MustCompile(`(/page)/(\d+)`)
var commentReg = regexp.MustCompile(`/comment-page-(\d+)`)
func (p PageEle) Url(path, query string, page int) string {
if !strings.Contains(path, "/page/") {
@ -67,3 +83,32 @@ func (p PageEle) Url(path, query string, page int) string {
}
return str.Join(path, query)
}
func (p CommentPageEle) Url(path, query string, page int) string {
if !strings.Contains(path, "/comment-page-") {
path = fmt.Sprintf("%s%s", path, "/comment-page-1")
}
path = commentReg.ReplaceAllString(path, fmt.Sprintf("/comment-page-%d", page))
path = strings.Replace(path, "//", "/", -1)
if path == "" {
path = "/"
}
return str.Join(path, query)
}
func (p CommentPageEle) Middle(page int, url string) string {
return ""
}
func (p CommentPageEle) Dots() string {
return ""
}
func (p CommentPageEle) Current(page, totalPage, totalRow int) string {
return ""
}
func (p CommentPageEle) Prev(url string) string {
return fmt.Sprintf(p.PrevEle, url, helper.Or(wpconfig.GetOption("comment_order") == "asc", "较早评论", "较新评论"))
}
func (p CommentPageEle) Next(url string) string {
return fmt.Sprintf(p.NextEle, url, helper.Or(wpconfig.GetOption("comment_order") == "asc", "较新评论", "较早评论"))
}

View File

@ -52,14 +52,30 @@
{{ if .showComment}}
<div id="comments" class="comments-area">
{{ if gt .post.CommentCount 0}}
<h2 class="comments-title">《{{.post.PostTitle}}》上有{{.post.CommentCount}}条评论 </h2>
<ol class="comment-list">
{{.comments|unescaped}}
</ol>
{{ if ne .comments ""}}
<h2 class="comments-title">《{{.post.PostTitle}}》上有{{.totalCommentNum}}条评论 </h2>
{{if gt .totalCommentPage 1}}
<nav class="navigation comment-navigation">
<h2 class="screen-reader-text">评论导航</h2>
<div class="nav-links">
{{ .commentPageNav|unescaped}}
</div><!-- .nav-links -->
</nav>
{{end}}
<ol class="comment-list">
{{.comments|unescaped}}
</ol>
{{if gt .totalCommentPage 1}}
<nav class="navigation comment-navigation">
<h2 class="screen-reader-text">评论导航</h2>
<div class="nav-links">
{{ .commentPageNav|unescaped}}
</div><!-- .nav-links -->
</nav>
{{end}}
{{end}}
{{if and (eq .post.CommentStatus "open") (eq ( "thread_comments" |getOption ) "1")}}
{{if eq .post.CommentStatus "open"}}
<div id="respond" class="comment-respond">
<h3 id="reply-title" class="comment-reply-title">发表回复
<small>

View File

@ -1,6 +1,7 @@
package twentyseventeen
import (
"context"
"fmt"
"github.com/fthvgb1/wp-go/app/pkg/constraints"
"github.com/fthvgb1/wp-go/app/pkg/constraints/widgets"
@ -14,7 +15,6 @@ import (
"github.com/fthvgb1/wp-go/cache/reload"
"github.com/fthvgb1/wp-go/helper"
str "github.com/fthvgb1/wp-go/helper/strings"
"github.com/gin-gonic/gin"
"strings"
)
@ -103,7 +103,7 @@ type comment struct {
plugins.CommonCommentFormat
}
func (c comment) FormatLi(ctx *gin.Context, m models.Comments, depth int, isTls bool, eo, parent string) string {
func (c comment) FormatLi(_ context.Context, m models.Comments, depth, maxDepth, page int, isTls, isThreadComments bool, eo, parent string) string {
templ := plugins.CommonLi()
templ = strings.ReplaceAll(templ, `<a rel="nofollow" class="comment-reply-link"
href="/p/{{PostId}}?replytocom={{CommentId}}#respond" data-commentid="{{CommentId}}" data-postid="{{PostId}}"
@ -114,7 +114,7 @@ func (c comment) FormatLi(ctx *gin.Context, m models.Comments, depth int, isTls
data-belowelement="div-comment-{{CommentId}}" data-respondelement="respond"
data-replyto="回复给{{CommentAuthor}}"
aria-label="回复给{{CommentAuthor}}"><svg class="icon icon-mail-reply" aria-hidden="true" role="img"> <use href="#icon-mail-reply" xlink:href="#icon-mail-reply"></use> </svg>回复</a>`)
return plugins.FormatLi(templ, ctx, m, depth, isTls, eo, parent)
return plugins.FormatLi(templ, m, depth, maxDepth, page, isTls, isThreadComments, eo, parent)
}
func postThumbnail(h *wp.Handle, posts *models.Posts) {

103
app/theme/wp/comments.go Normal file
View File

@ -0,0 +1,103 @@
package wp
import (
"context"
"github.com/fthvgb1/wp-go/app/pkg/models"
"github.com/fthvgb1/wp-go/app/plugins"
"github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/cache"
"github.com/fthvgb1/wp-go/cache/cachemanager"
"github.com/fthvgb1/wp-go/helper/slice"
str "github.com/fthvgb1/wp-go/helper/strings"
"time"
)
func RenderComment(ctx context.Context, page int, render plugins.CommentHtml, ids []uint64, timeout time.Duration, isTLS bool) (string, error) {
ca, _ := cachemanager.GetMapCache[uint64, models.PostComments]("postCommentData")
h := CommentHandle{
maxDepth: str.ToInteger(wpconfig.GetOption("thread_comments_depth"), 5),
depth: 1,
isTls: isTLS,
html: render,
order: wpconfig.GetOption("comment_order"),
ca: ca,
threadComments: wpconfig.GetOption("thread_comments") == "1",
page: page,
}
return h.formatComments(ctx, ids, timeout)
}
type CommentHandle struct {
maxDepth int
depth int
isTls bool
html plugins.CommentHtml
order string
page int
ca *cache.MapCache[uint64, models.PostComments]
threadComments bool
}
func (c CommentHandle) findComments(ctx context.Context, timeout time.Duration, comments []models.PostComments) ([]models.PostComments, error) {
rr := slice.FilterAndMap(comments, func(t models.PostComments) ([]uint64, bool) {
return t.Children, len(t.Children) > 0
})
if len(rr) < 1 {
slice.Sort(comments, func(i, j models.PostComments) bool {
return c.html.FloorOrder(c.order, i, j)
})
return comments, nil
}
ids := slice.Decompress(rr)
r, err := c.ca.GetCacheBatch(ctx, ids, timeout)
if err != nil {
return nil, err
}
rrr, err := c.findComments(ctx, timeout, r)
if err != nil {
return nil, err
}
comments = slice.Map(comments, func(t models.PostComments) models.PostComments {
t.Children = nil
return t
})
comments = append(comments, rrr...)
return comments, nil
}
func (c CommentHandle) formatComments(ctx context.Context, ids []uint64, timeout time.Duration) (html string, err error) {
comments, err := c.ca.GetCacheBatch(ctx, ids, timeout)
if err != nil {
return "", err
}
if c.depth >= c.maxDepth {
comments, err = c.findComments(ctx, timeout, comments)
}
s := str.NewBuilder()
for i, comment := range comments {
eo := "even"
if (i+1)%2 == 0 {
eo = "odd"
}
parent := ""
fl := false
if c.threadComments && len(comment.Children) > 0 && c.depth < c.maxDepth+1 {
parent = "parent"
fl = true
}
s.WriteString(c.html.FormatLi(ctx, comment.Comments, c.depth, c.maxDepth, c.page, c.isTls, c.threadComments, eo, parent))
if fl {
c.depth++
ss, err := c.formatComments(ctx, comment.Children, timeout)
if err != nil {
return "", err
}
s.WriteString(`<ol class="children">`, ss, `</ol>`)
c.depth--
}
s.WriteString("</li><!-- #comment-## -->")
}
html = s.String()
return
}

View File

@ -10,15 +10,22 @@ import (
"github.com/fthvgb1/wp-go/app/plugins"
"github.com/fthvgb1/wp-go/app/plugins/wpposts"
"github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/cache/cachemanager"
"github.com/fthvgb1/wp-go/helper/number"
str "github.com/fthvgb1/wp-go/helper/strings"
"github.com/fthvgb1/wp-go/plugin/pagination"
"net/http"
"time"
)
type DetailHandle struct {
*Handle
CommentRender plugins.CommentHtml
Comments []models.Comments
Comments []uint64
Page int
Limit int
Post models.Posts
TotalRaw int
}
func NewDetailHandle(handle *Handle) *DetailHandle {
@ -80,11 +87,29 @@ func (d *DetailHandle) PasswordProject() {
}
}
func (d *DetailHandle) Comment() {
comments, err := cache.PostComments(d.C, d.Post.Id)
logs.IfError(err, "get d.Post comment", d.Post.Id)
d.ginH["comments"] = comments
d.Comments = comments
err := cache.UpdateCommentCache(d.C, time.Second, d.Post.Id)
d.ginH["totalCommentNum"] = 0
d.ginH["totalCommentPage"] = 1
d.ginH["commentPageNav"] = ""
d.ginH["commentOrder"] = wpconfig.GetOption("comment_order")
logs.IfError(err, "increase update comments err")
d.Page = str.ToInteger(d.C.Param("page"), 1)
d.ginH["currentPage"] = d.Page
d.Limit = str.ToInteger(wpconfig.GetOption("comments_per_page"), 5)
ids, totalCommentNum, err := cachemanager.Pagination[uint64]("PostCommentsIds", d.C, time.Second, d.Post.Id, d.Page, d.Limit, "desc")
if err != nil {
d.SetErr(err)
return
}
d.TotalRaw = totalCommentNum
num, err := cachemanager.Get[int]("commentNumber", d.C, d.Post.Id, time.Second)
if err != nil {
d.SetErr(err)
return
}
d.ginH["totalCommentNum"] = num
d.ginH["totalCommentPage"] = number.DivideCeil(totalCommentNum, d.Limit)
d.Comments = ids
}
func (d *DetailHandle) RenderComment() {
@ -97,10 +122,19 @@ func (d *DetailHandle) RenderComment() {
ableComment = false
}
d.ginH["showComment"] = ableComment
if len(d.Comments) > 0 && ableComment {
dep := str.ToInteger(wpconfig.GetOption("thread_comments_depth"), 5)
d.ginH["comments"] = plugins.FormatComments(d.C, d.CommentRender, d.Comments, dep)
d.ginH["comments"] = ""
if len(d.Comments) < 0 || !ableComment {
return
}
var err error
d.ginH["comments"], err = RenderComment(d.C, d.Page, d.CommentRender, d.Comments, 2*time.Second, d.IsHttps())
if err != nil {
d.SetErr(err)
return
}
paginations := pagination.NewParsePagination(d.TotalRaw, d.Limit, d.Page, 1, d.C.Request.URL.RawQuery, d.C.Request.URL.Path)
d.ginH["commentPageNav"] = pagination.Paginate(plugins.TwentyFifteenCommentPagination(), paginations)
}
func (d *DetailHandle) ContextPost() {

View File

@ -185,7 +185,9 @@ func NewPaginationCache[K comparable, V any](m *cache.MapCache[string, helper.Pa
if fet == nil {
fet = reload.FnVal(str.Join("paginationCache-", name, "-fetchNum"), fetchNum, nil)
}
return cache.NewPagination(m, ma, dbFn, localFn, dbKeyFn, localKeyFn, fet, name)
p := cache.NewPagination(m, ma, dbFn, localFn, dbKeyFn, localKeyFn, fet, name)
mapCache.Store(name, p)
return p
}
func NewMapCache[K comparable, V any](data cache.Cache[K, V], batchFn cache.MapBatchFn[K, V], fn cache.MapSingleFn[K, V], args ...any) *cache.MapCache[K, V] {
@ -219,9 +221,9 @@ func SetExpireTime(c cache.SetTime, name string, expireTime time.Duration, expir
c.SetExpiredTime(fn)
}
func ChangeExpireTime(t time.Duration, name ...string) {
func ChangeExpireTime(t time.Duration, coverConf bool, name ...string) {
for _, s := range name {
reload.ChangeFnVal(s, t)
reload.ChangeFnVal(s, t, coverConf)
}
}
@ -291,3 +293,28 @@ func GetMapCache[K comparable, V any](name string) (*cache.MapCache[K, V], bool)
vv, err := getMap[K, V](name)
return vv, err == nil
}
func GetPaginationCache[K comparable, V any](name string) (*cache.Pagination[K, V], bool) {
v, err := getPagination[K, V](name)
return v, err == nil
}
func Pagination[V any, K comparable](name string, ctx context.Context, timeout time.Duration, k K, page, limit int, a ...any) ([]V, int, error) {
v, err := getPagination[K, V](name)
if err != nil {
return nil, 0, err
}
return v.Pagination(ctx, timeout, k, page, limit, a...)
}
func getPagination[K comparable, T any](name string) (*cache.Pagination[K, T], error) {
m, ok := mapCache.Load(name)
if !ok {
return nil, errors.New(str.Join("cache ", name, " doesn't exist"))
}
vk, ok := m.(*cache.Pagination[K, T])
if !ok {
return nil, errors.New(str.Join("cache ", name, " type error"))
}
return vk, nil
}

View File

@ -72,7 +72,7 @@ func TestSetExpireTime(t *testing.T) {
fmt.Println(c.Get(ctx, "xx"))
time.Sleep(time.Second)
fmt.Println(c.Get(ctx, "xx"))
ChangeExpireTime(3*time.Second, "xx")
ChangeExpireTime(3*time.Second, true, "xx")
c.Set(ctx, "xx", "yyy")
time.Sleep(time.Second)
fmt.Println(c.Get(ctx, "xx"))

49
cache/pagination.go vendored
View File

@ -30,7 +30,14 @@ type DbFn[K comparable, V any] func(ctx context.Context, k K, page, limit, total
type LocalFn[K comparable, V any] func(ctx context.Context, data []V, k K, page, limit int, a ...any) ([]V, int, error)
func NewPagination[K comparable, V any](m *MapCache[string, helper.PaginationData[V]], maxNum func() int, dbFn DbFn[K, V], localFn LocalFn[K, V], dbKeyFn, localKeyFn func(K, ...any) string, batchFetchNum func() int, name string) *Pagination[K, V] {
func (p *Pagination[K, V]) IsSwitchDB(k K) bool {
v, _ := p.isSwitch.Load(k)
return v == true
}
func NewPagination[K comparable, V any](m *MapCache[string, helper.PaginationData[V]], maxNum func() int,
dbFn DbFn[K, V], localFn LocalFn[K, V], dbKeyFn, localKeyFn func(K, ...any) string,
batchFetchNum func() int, name string) *Pagination[K, V] {
if dbKeyFn == nil {
dbKeyFn = func(k K, a ...any) string {
s := str.NewBuilder()
@ -66,6 +73,7 @@ func (p *Pagination[K, V]) Pagination(ctx context.Context, timeout time.Duration
data, total, err := p.paginationByLocal(ctx, timeout, k, page, limit, a...)
if err != nil {
if errors.Is(err, switchDb) {
p.isSwitch.Store(k, true)
err = nil
return p.paginationByDB(ctx, timeout, k, page, limit, total, a...)
}
@ -101,36 +109,47 @@ func (p *Pagination[K, V]) paginationByLocal(ctx context.Context, timeout time.D
if err != nil {
return nil, 0, err
}
if totalRaw < 1 {
data.Data = nil
data.TotalRaw = 0
p.Set(ctx, key, data)
return nil, 0, nil
}
if totalRaw >= p.maxNum() {
p.isSwitch.Store(k, true)
return nil, totalRaw, switchDb
}
if len(da) < totalRaw {
totalPage := number.DivideCeil(totalRaw, batchNum)
for i := 1; i <= totalPage; i++ {
daa, _, err := p.fetchDb(ctx, timeout, k, i, batchNum, totalRaw, a...)
if err != nil {
return nil, 0, err
}
da = append(da, daa...)
totalPage := number.DivideCeil(totalRaw, batchNum)
for i := 1; i <= totalPage; i++ {
daa, _, err := p.fetchDb(ctx, timeout, k, i, batchNum, totalRaw, a...)
if err != nil {
return nil, 0, err
}
data.Data = da
data.TotalRaw = totalRaw
p.Set(ctx, key, data)
da = append(da, daa...)
}
data.Data = da
data.TotalRaw = totalRaw
p.Set(ctx, key, data)
return p.localFn(ctx, data.Data, k, page, limit, a...)
}
func (p *Pagination[K, V]) dbGet(ctx context.Context, key string) (helper.PaginationData[V], bool) {
data, ok := p.Get(ctx, key)
if ok && p.increaseUpdate != nil && p.increaseUpdate.CycleTime() > p.GetExpireTime(ctx)-p.Ttl(ctx, key) {
return data, true
}
return data, false
}
func (p *Pagination[K, V]) paginationByDB(ctx context.Context, timeout time.Duration, k K, page, limit, totalRaw int, a ...any) ([]V, int, error) {
key := p.dbKeyFn(k, append([]any{page, limit}, a...)...)
data, ok := p.Get(ctx, key)
data, ok := p.dbGet(ctx, key)
if ok {
return data.Data, data.TotalRaw, nil
}
p.mux.Lock()
defer p.mux.Unlock()
data, ok = p.Get(ctx, key)
data, ok = p.dbGet(ctx, key)
if ok {
return data.Data, data.TotalRaw, nil
}

View File

@ -304,7 +304,7 @@ func FnVal[T any](name string, t T, fn func() T) func() T {
}
}
func ChangeFnVal[T any](name string, val T) {
func ChangeFnVal[T any](name string, val T, coverConf bool) {
v, ok := anyMap.Load(name)
if !ok {
return
@ -313,7 +313,7 @@ func ChangeFnVal[T any](name string, val T) {
if !ok {
return
}
if !vv.isUseManger.Load() {
if coverConf && !vv.isUseManger.Load() {
vv.isUseManger.Store(true)
}
vv.v.Store(val)

View File

@ -8,22 +8,22 @@ import (
func TestFlushMapVal(t *testing.T) {
t.Run("t1", func(t *testing.T) {
c := 0
v := GetAnyValMapBy("key", 2, struct{}{}, func(a struct{}) int {
v := GetAnyValMapBy("key", 2, struct{}{}, func(a struct{}) (int, bool) {
c++
return 33
return 33, true
})
fmt.Println(v)
FlushMapVal("key", 2)
v = GetAnyValMapBy("key", 2, struct{}{}, func(a struct{}) int {
v = GetAnyValMapBy("key", 2, struct{}{}, func(a struct{}) (int, bool) {
fmt.Println("xxxxx")
return 33
return 33, true
})
fmt.Println(v)
FlushAnyVal("key")
v = GetAnyValMapBy("key", 2, struct{}{}, func(a struct{}) int {
v = GetAnyValMapBy[int, int, struct{}]("key", 2, struct{}{}, func(a struct{}) (int, bool) {
fmt.Println("yyyy")
return 33
return 33, true
})
fmt.Println(v)
})

5
cache/vars.go vendored
View File

@ -156,7 +156,10 @@ type VarMemoryCache[T any] struct {
}
func (c *VarMemoryCache[T]) ClearExpired(ctx context.Context) {
c.Flush(ctx)
_, ok := c.Get(ctx)
if !ok {
c.Flush(ctx)
}
}
func NewVarMemoryCache[T any](expireTime func() time.Duration) *VarMemoryCache[T] {

View File

@ -185,7 +185,7 @@ func DescEahByKey[K constraints.Ordered, V any](m map[K]V, fn func(K, V)) {
orderedEahByKey(m, slice.ASC, fn)
}
func orderedEahByKey[K constraints.Ordered, V any](m map[K]V, ordered int, fn func(K, V)) {
func orderedEahByKey[K constraints.Ordered, V any](m map[K]V, ordered string, fn func(K, V)) {
keys := Keys(m)
slice.Sorts(keys, ordered)
for _, key := range keys {

View File

@ -125,12 +125,23 @@ func Pagination[T any](arr []T, page, pageSize int) []T {
if start > l {
start = l
}
end := page * pageSize
end := start + pageSize
if l < end {
end = l
}
return arr[start:end]
}
func ReversePagination[T any](arr []T, page, pageSize int) []T {
end := len(arr) - (page-1)*pageSize
if end <= 0 {
return nil
}
start := end - pageSize
if start < 0 {
start = 0
}
return arr[start:end]
}
func Chunk[T any](arr []T, size int) [][]T {
var r [][]T

View File

@ -6,8 +6,8 @@ import (
)
const (
ASC = iota
DESC
ASC = "asc"
DESC = "desc"
)
type anyArr[T any] struct {
@ -44,7 +44,7 @@ func StableSort[T any](arr []T, fn func(i, j T) bool) {
sort.Stable(slice)
}
func Sorts[T constraints.Ordered](a []T, order int) {
func Sorts[T constraints.Ordered](a []T, order string) {
slice := anyArr[T]{
data: a,
fn: func(i, j T) bool {
@ -56,8 +56,23 @@ func Sorts[T constraints.Ordered](a []T, order int) {
}
sort.Sort(slice)
}
func SortsNew[T constraints.Ordered](a []T, order string) []T {
r := make([]T, len(a))
copy(r, a)
slice := anyArr[T]{
data: r,
fn: func(i, j T) bool {
if order == DESC {
return i > j
}
return i < j
},
}
sort.Sort(slice)
return r
}
func SimpleSort[T any, O constraints.Ordered](a []T, order int, fn func(t T) O) {
func SimpleSort[T any, O constraints.Ordered](a []T, order string, fn func(t T) O) {
slice := anyArr[T]{
data: a,
fn: func(i, j T) bool {

View File

@ -74,7 +74,7 @@ func TestSort(t *testing.T) {
func TestSorts(t *testing.T) {
type args[T constraints.Ordered] struct {
a []T
order int
order string
}
type testCase[T constraints.Ordered] struct {
name string
@ -169,7 +169,7 @@ func TestSimpleSort(t *testing.T) {
type args[T any, O constraints.Ordered] struct {
a []T
order int
order string
fn func(t T) O
}
type testCase[T any, O constraints.Ordered] struct {