Compare commits

...

114 Commits

Author SHA1 Message Date
8aeec60181 optimize code and update package version 2024-08-31 20:01:52 +08:00
58d3e4f645 optimize code and update package version 2024-08-21 21:11:27 +08:00
e592a753bf add some func 2024-06-24 21:19:24 +08:00
d598fb5ce5 add some func 2024-06-23 21:30:57 +08:00
63f769796f add some func 2024-06-23 19:18:54 +08:00
7cbfdf8601 update and add func 2024-06-22 11:32:48 +08:00
9c60d10568 optimize sign mechanism 2024-06-19 16:10:42 +08:00
dcbe760f09 optimize code 2024-06-12 22:38:49 +08:00
d220bb2a6c optimize code 2024-06-12 17:51:47 +08:00
eb7ccd4f2d remake safety slice 2024-06-12 00:14:07 +08:00
39c9dc1b09 add rwmap 2024-06-11 22:10:34 +08:00
68beb0fcbb optimize code 2024-06-11 02:21:57 +08:00
1f5f7f465d add httptool.BodyBuffer 2024-06-10 22:50:18 +08:00
b7c09cb5a2 add zeroDefault func and optimize httptool and update package 2024-06-10 17:44:41 +08:00
5942f76972 fix bug 2024-06-06 22:59:03 +08:00
1f40810b78 optimize wp reg route 2024-04-18 11:48:49 +08:00
da858d55e0 fix bug 2024-04-18 11:26:58 +08:00
166bb08ed0 optimize code and add hook gin action and set noroute 2024-04-18 01:39:36 +08:00
5eaf798a6c optimize reload schema and optimize cachemanager package 2024-04-15 15:25:19 +08:00
e2e6bcc8ce fix bug 2024-04-14 23:08:22 +08:00
daa7101493 optimize reload package add append executed once func when reload and add hook scheme 2024-04-14 22:49:40 +08:00
6f51f1c295 update docker golang version 2024-04-13 22:12:20 +08:00
a8d1dcdd5b optimize code and fix bug 2024-04-10 23:48:23 +08:00
9af2257650 fix bug 2024-04-10 22:41:26 +08:00
63591899bb optimize code 2024-04-10 22:31:08 +08:00
ee9ba3fcf0 revise digest add more complex config 2024-04-09 23:51:45 +08:00
a78815f3d3 revise digest add set tag occupied number 2024-04-07 21:30:57 +08:00
6209f2b5e5 optimize digest 2024-04-07 20:02:39 +08:00
08d3bca39c upgrade dependency packages 2024-04-06 20:41:45 +08:00
9b8e597066 remove SetSqlxDB func 2024-04-06 16:44:56 +08:00
3215b0c8ea add get customize config func and add wpHandle add err level 2024-04-06 16:43:18 +08:00
c7c97c469f optimize code 2024-04-05 16:55:34 +08:00
b45ad0e54e fix enlighterjs config 2024-04-05 16:23:59 +08:00
a0c6e48e83 add customheader err measure 2024-04-05 11:53:44 +08:00
3c3ed73971 db use context method 2024-03-27 13:09:46 +08:00
be58a35245 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	README.md
#	readme_en.md
2024-03-22 12:24:51 +08:00
67bdf1e070 change logo size 2024-03-22 12:23:34 +08:00
981e0ad85b change logo size 2024-03-22 12:21:59 +08:00
74159940b5 add thanks jetbrains product license support 2024-03-22 12:14:12 +08:00
206e5902d1 add running description 2024-03-21 17:09:11 +08:00
2d38b36525 optimize pagination interface and update dependence package 2024-03-18 19:12:16 +08:00
af712f06a5 fix bug and optimize code 2024-01-25 14:12:51 +08:00
73575965d3 update docker go version 2024-01-24 15:25:12 +08:00
f49ad731e3 optimize code and fix bug 2024-01-24 15:13:29 +08:00
802c7c7dcc optimize code 2024-01-24 00:25:37 +08:00
20ea36c727 fix bug and optimize code and update dependence package version 2024-01-23 22:59:15 +08:00
fd3199a83c remove sitetest 2024-01-22 21:14:16 +08:00
e25f679ea6 optimize code 2024-01-22 20:29:17 +08:00
4842d53316 fix bug and optimize code 2024-01-21 21:29:36 +08:00
65bfccdd48 fix bug 2024-01-19 22:36:44 +08:00
c60146d614 optimize code and fix bug 2024-01-19 15:18:55 +08:00
9285698ee4 rename a func 2024-01-19 00:14:04 +08:00
5e18c9babd optimize code, fix bug and add some comments 2024-01-18 23:29:50 +08:00
8bdc53d175 fix bug 2024-01-15 13:25:29 +08:00
ad9bdc7574 optimize code 2024-01-14 22:07:16 +08:00
0352dd0fd0 fix bug, optimize code, add some cache comments, add middleware,change pagination nav to mix url plain 2024-01-13 21:15:54 +08:00
6044b8eaec perfect comments relates display and optimize some code 2024-01-10 23:50:23 +08:00
8d3197ee98 optimize 2024-01-05 00:01:57 +08:00
8fcf3ecca2 fix bug 2024-01-04 23:36:00 +08:00
4d9d011213 remove commentIncreaseUpdate simplify comment cache 2024-01-04 23:32:10 +08:00
12b75b9d82 optimize mutex lock fn 2023-12-28 20:42:11 +08:00
568ab15a34 map cache add mutex lock fn 2023-12-26 23:09:30 +08:00
c38b62c82a fix bug 2023-12-24 00:20:35 +08:00
0774b122ee fix bug 2023-12-23 22:49:34 +08:00
f7d2377101 optimize pagination interface and add twentyseventeen comments pagination nav 2023-12-23 20:17:42 +08:00
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
f257a966e6 fix pre comment render 2023-12-13 11:26:22 +08:00
ceffeccf8d fix comment render bug 2023-12-12 18:59:08 +08:00
eed45f51ba fix bug and add redis cache drive example 2023-12-11 22:32:00 +08:00
d72bed0c8c refine cache 2023-12-10 19:15:49 +08:00
0cdb3ba040 refine pagination cache 2023-12-10 17:32:49 +08:00
227de8bdc8 add pagination cache 2023-12-08 21:33:09 +08:00
74304b5b12 fix slice.range,pipe middleware bug, add slice.simpleOrder func 2023-12-05 21:39:13 +08:00
ac29cf2448 code optimize 2023-12-03 22:42:44 +08:00
137b2a9738 var cache increaseUpdate 2023-11-29 18:24:41 +08:00
9f49a274cd fix bugs 2023-11-29 00:00:25 +08:00
7c3f8baaa2 map cache add increase interface 2023-11-28 22:46:22 +08:00
547d8e59e6 optimize cache add set cache function 2023-11-25 17:49:20 +08:00
7bbc961f72 optimize 2023-11-16 22:52:14 +08:00
66c02f7fc9 update dependent package 2023-11-13 22:53:20 +08:00
a5294e2e20 revise reload package 2023-11-12 21:39:04 +08:00
7978e9ee74 optimize 2023-11-07 15:39:38 +08:00
f6a5cf4255 optimize 2023-11-07 15:36:00 +08:00
928a608878 change package name 2023-11-07 15:19:44 +08:00
42d2795a05 map cache add func get map 2023-11-07 15:18:34 +08:00
041d06104b optimize cachemanger improve var cache 2023-11-02 22:40:13 +08:00
d1fb560578 fix bug and test set expireTime 2023-10-31 19:23:38 +08:00
42484e3f96 fix 2023-10-30 22:21:08 +08:00
64a2c2e33b optimize and expand map cache remove ver add dynamic set expired time 2023-10-30 21:52:15 +08:00
86d1616732 fix bug 2023-10-29 18:58:06 +08:00
2eb58f732b expend cache map 2023-10-29 18:46:01 +08:00
936d033547 fix 2023-10-28 18:40:21 +08:00
cd4787991d optimize optimize...... 2023-10-28 18:37:00 +08:00
71ddf299e4 optimize again again...... 2023-10-28 15:19:39 +08:00
0f467bbc98 optimize again 2023-10-28 15:03:14 +08:00
171b3bc59c optimize again 2023-10-28 14:50:06 +08:00
f369cf4f22 optimize 2023-10-28 13:30:32 +08:00
ddf6e62e5a fix 2023-10-28 00:08:04 +08:00
e502f581e1 fix cache bug 2023-10-27 23:57:15 +08:00
acb064b762 improve cache interface and memorymapcache.go implement 2023-10-27 20:51:46 +08:00
f0c1744998 fix 2023-10-26 21:45:02 +08:00
876a81c062 improve map cache and enhance cachemanager add delete element and get any val 2023-10-26 21:38:31 +08:00
78f5157efe fix 2023-10-24 16:07:48 +08:00
acbcbf455f improve 2023-10-24 16:06:59 +08:00
179545f2bb reload_test.go 2023-10-24 16:05:33 +08:00
48a3fe6588 fix 2023-10-23 22:47:21 +08:00
3fd4f412b1 improve 2023-10-23 22:38:52 +08:00
5273f4ecf9 reload optimize add flush single element 2023-10-23 22:32:20 +08:00
b9c2527f4c update package 2023-10-14 19:57:14 +08:00
1286338af0 http tool, fix digest, reload add sort 2023-10-14 14:57:57 +08:00
21a4ff3b3e http tool get 2023-10-04 21:48:21 +08:00
2302aa7ff8 fix 2023-08-26 22:03:49 +08:00
bc71be7238 func rename and little optimize 2023-08-26 22:01:20 +08:00
136 changed files with 6821 additions and 2103 deletions

3
.gitignore vendored
View File

@ -3,4 +3,5 @@
/config.yaml /config.yaml
err.log err.log
/plugins/ /plugins/
/config.json /config.json
go.sum

View File

@ -1,8 +1,7 @@
FROM golang:latest as gobulidIso FROM golang:1.22.2-alpine as gobulidIso
COPY ./ /go/src/wp-go COPY ./ /go/src/wp-go
WORKDIR /go/src/wp-go WORKDIR /go/src/wp-go
ENV GOPROXY="https://goproxy.cn" RUN go build -ldflags "-w" -tags netgo -o wp-go app/cmd/main.go
RUN go build -ldflags "-w" -tags netgo -o wp-go internal/cmd/main.go
FROM alpine:latest FROM alpine:latest
WORKDIR /opt/wp-go WORKDIR /opt/wp-go

View File

@ -20,6 +20,11 @@
- kill -SIGUSR1 PID 更新配置和清空缓存 - kill -SIGUSR1 PID 更新配置和清空缓存
- kill -SIGUSR2 PID 清空缓存 - kill -SIGUSR2 PID 清空缓存
#### 运行
```
go run app/cmd/main.go [-c configpath] [-p port]
```
#### 数据显示支持程度 #### 数据显示支持程度
| 页表 | 支持程度 | | 页表 | 支持程度 |
@ -48,8 +53,10 @@
- 博客页面至多显示数量 - 博客页面至多显示数量
- Feed中显示最近数量 - Feed中显示最近数量
- 讨论 - 讨论
- 其他评论设置-启用评论嵌套,最多嵌套层数 - 其他评论设置
- 在每个页面顶部显示 `新旧`评论 - `启用|禁止`评论嵌套,最多嵌套层数
- 分页显示评论,每页显示评论条数,默认显示`最前/后`页
- 在每个页面顶部显示 `新旧`评论
#### 主题支持程度 #### 主题支持程度
@ -72,4 +79,9 @@
#### 其它 #### 其它
用的gin框架和sqlx,在外面封装了层查询的方法。 用的gin框架和sqlx,在外面封装了层查询的方法。
#### 鸣谢
<a href="https://jb.gg/OpenSourceSupport"><img src="https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.png" alt="JetBrains Logo (Main) logo." width="30%"></a>

View File

@ -11,6 +11,7 @@ import (
"github.com/fthvgb1/wp-go/app/pkg/logs" "github.com/fthvgb1/wp-go/app/pkg/logs"
"github.com/fthvgb1/wp-go/app/wpconfig" "github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/helper/slice" "github.com/fthvgb1/wp-go/helper/slice"
str "github.com/fthvgb1/wp-go/helper/strings"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10" "github.com/go-playground/validator/v10"
"io" "io"
@ -77,7 +78,7 @@ func PostComment(c *gin.Context) {
} }
req.Host = home.Host req.Host = home.Host
res, err := cli.Do(req) res, err := cli.Do(req)
if err != nil && err != http.ErrUseLastResponse { if err != nil && !errors.Is(err, http.ErrUseLastResponse) {
logs.Error(err, "请求评论接口错误") logs.Error(err, "请求评论接口错误")
return return
} }
@ -110,6 +111,9 @@ func PostComment(c *gin.Context) {
} }
cc := c.Copy() cc := c.Copy()
go func() { go func() {
if gin.Mode() != gin.ReleaseMode {
return
}
id := comment.CommentPostId id := comment.CommentPostId
if id <= 0 { if id <= 0 {
logs.Error(errors.New("获取文档id错误"), "", comment.CommentPostId) logs.Error(errors.New("获取文档id错误"), "", comment.CommentPostId)
@ -130,8 +134,15 @@ func PostComment(c *gin.Context) {
err = er err = er
return return
} }
cache.NewCommentCache().Set(c, up.RawQuery, string(s)) uuu := ""
c.Redirect(http.StatusFound, res.Header.Get("Location")) uu, _ := url.Parse(res.Header.Get("Location"))
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 return
} }
var r io.Reader var r io.Reader

View File

@ -10,6 +10,30 @@ import (
var tmp = "Mon, 02 Jan 2006 15:04:05 GMT" var tmp = "Mon, 02 Jan 2006 15:04:05 GMT"
func Feed(c *gin.Context) {
v, ok := c.GetQuery("feed")
if !ok || v == "" {
c.Next()
return
}
switch v {
case "rss2":
p, ok := c.GetQuery("p")
if ok && p != "" {
c.AddParam("id", p)
PostFeed(c)
} else {
SiteFeed(c)
}
c.Abort()
return
case "comments-rss2":
CommentsFeed(c)
c.Abort()
return
}
}
func isCacheExpired(c *gin.Context, lastTime time.Time) bool { func isCacheExpired(c *gin.Context, lastTime time.Time) bool {
eTag := str.Md5(lastTime.Format(tmp)) eTag := str.Md5(lastTime.Format(tmp))
since := c.Request.Header.Get("If-Modified-Since") since := c.Request.Header.Get("If-Modified-Since")
@ -24,20 +48,21 @@ func isCacheExpired(c *gin.Context, lastTime time.Time) bool {
return true return true
} }
func Feed(c *gin.Context) { func SiteFeed(c *gin.Context) {
if !isCacheExpired(c, cache.FeedCache().GetLastSetTime()) { feed := cache.FeedCache()
if !isCacheExpired(c, feed.GetLastSetTime(c)) {
c.Status(http.StatusNotModified) c.Status(http.StatusNotModified)
return return
} }
r, err := cache.FeedCache().GetCache(c, time.Second, c) r, err := feed.GetCache(c, time.Second, c)
if err != nil { if err != nil {
c.Status(http.StatusInternalServerError) c.Status(http.StatusInternalServerError)
c.Abort() c.Abort()
c.Error(err) c.Error(err)
return return
} }
setFeed(r[0], c, cache.FeedCache().GetLastSetTime()) setFeed(r[0], c, feed.GetLastSetTime(c))
} }
func setFeed(s string, c *gin.Context, t time.Time) { func setFeed(s string, c *gin.Context, t time.Time) {
@ -51,31 +76,33 @@ func setFeed(s string, c *gin.Context, t time.Time) {
func PostFeed(c *gin.Context) { func PostFeed(c *gin.Context) {
id := c.Param("id") id := c.Param("id")
if !isCacheExpired(c, cache.PostFeedCache().GetLastSetTime(c, id)) { postFeed := cache.PostFeedCache()
if !isCacheExpired(c, postFeed.GetLastSetTime(c, id)) {
c.Status(http.StatusNotModified) c.Status(http.StatusNotModified)
return return
} }
s, err := cache.PostFeedCache().GetCache(c, id, time.Second, c, id) s, err := postFeed.GetCache(c, id, time.Second)
if err != nil { if err != nil {
c.Status(http.StatusInternalServerError) c.Status(http.StatusInternalServerError)
c.Abort() c.Abort()
c.Error(err) c.Error(err)
return return
} }
setFeed(s, c, cache.PostFeedCache().GetLastSetTime(c, id)) setFeed(s, c, postFeed.GetLastSetTime(c, id))
} }
func CommentsFeed(c *gin.Context) { func CommentsFeed(c *gin.Context) {
if !isCacheExpired(c, cache.CommentsFeedCache().GetLastSetTime()) { feed := cache.CommentsFeedCache()
if !isCacheExpired(c, feed.GetLastSetTime(c)) {
c.Status(http.StatusNotModified) c.Status(http.StatusNotModified)
return return
} }
r, err := cache.CommentsFeedCache().GetCache(c, time.Second, c) r, err := feed.GetCache(c, time.Second, c)
if err != nil { if err != nil {
c.Status(http.StatusInternalServerError) c.Status(http.StatusInternalServerError)
c.Abort() c.Abort()
c.Error(err) c.Error(err)
return return
} }
setFeed(r[0], c, cache.CommentsFeedCache().GetLastSetTime()) setFeed(r[0], c, feed.GetLastSetTime(c))
} }

View File

@ -1,7 +1,6 @@
package actions package actions
import ( import (
"github.com/fthvgb1/wp-go/app/pkg/constraints"
"github.com/fthvgb1/wp-go/app/theme" "github.com/fthvgb1/wp-go/app/theme"
"github.com/fthvgb1/wp-go/app/theme/wp" "github.com/fthvgb1/wp-go/app/theme/wp"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -9,18 +8,8 @@ import (
func ThemeHook(scene string) func(*gin.Context) { func ThemeHook(scene string) func(*gin.Context) {
return func(c *gin.Context) { return func(c *gin.Context) {
s := scene t := theme.GetCurrentTheme()
if scene == constraints.Home { h := wp.NewHandle(c, scene, t)
if _, ok := c.GetQuery("s"); ok {
s = constraints.Search
}
}
t := theme.GetCurrentTemplateName()
h := wp.NewHandle(c, s, t)
h.Index = wp.NewIndexHandle(h)
h.Detail = wp.NewDetailHandle(h)
templ, _ := theme.GetTemplate(t)
h.SetTemplate(templ)
theme.Hook(t, h) theme.Hook(t, h)
} }
} }

View File

@ -1,53 +0,0 @@
package cachemanager
import (
"context"
"github.com/fthvgb1/wp-go/cache"
"time"
)
var ctx = context.Background()
type flush interface {
Flush(ctx context.Context)
}
type clear interface {
ClearExpired(ctx context.Context)
}
var clears []clear
var flushes []flush
func Flush() {
for _, f := range flushes {
f.Flush(ctx)
}
}
func MapCacheBy[K comparable, V any](fn func(...any) (V, error), expireTime time.Duration) *cache.MapCache[K, V] {
m := cache.NewMemoryMapCacheByFn[K, V](fn, expireTime)
FlushPush(m)
ClearPush(m)
return m
}
func MapBatchCacheBy[K comparable, V any](fn func(...any) (map[K]V, error), expireTime time.Duration) *cache.MapCache[K, V] {
m := cache.NewMemoryMapCacheByBatchFn[K, V](fn, expireTime)
FlushPush(m)
ClearPush(m)
return m
}
func FlushPush(f ...flush) {
flushes = append(flushes, f...)
}
func ClearPush(c ...clear) {
clears = append(clears, c...)
}
func ClearExpired() {
for _, c := range clears {
c.ClearExpired(ctx)
}
}

View File

@ -3,25 +3,21 @@ package main
import ( import (
"flag" "flag"
"fmt" "fmt"
"github.com/fthvgb1/wp-go/app/cmd/cachemanager" "github.com/fthvgb1/wp-go/app/ossigns"
"github.com/fthvgb1/wp-go/app/cmd/reload"
"github.com/fthvgb1/wp-go/app/cmd/route"
"github.com/fthvgb1/wp-go/app/mail"
"github.com/fthvgb1/wp-go/app/pkg/cache" "github.com/fthvgb1/wp-go/app/pkg/cache"
"github.com/fthvgb1/wp-go/app/pkg/config" "github.com/fthvgb1/wp-go/app/pkg/config"
"github.com/fthvgb1/wp-go/app/pkg/db" "github.com/fthvgb1/wp-go/app/pkg/db"
"github.com/fthvgb1/wp-go/app/pkg/logs" "github.com/fthvgb1/wp-go/app/pkg/logs"
"github.com/fthvgb1/wp-go/app/plugins" "github.com/fthvgb1/wp-go/app/plugins"
"github.com/fthvgb1/wp-go/app/plugins/wphandle" "github.com/fthvgb1/wp-go/app/plugins/wphandle"
"github.com/fthvgb1/wp-go/app/route"
"github.com/fthvgb1/wp-go/app/theme" "github.com/fthvgb1/wp-go/app/theme"
"github.com/fthvgb1/wp-go/app/wpconfig" "github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/cache/cachemanager"
"github.com/fthvgb1/wp-go/model" "github.com/fthvgb1/wp-go/model"
"log"
"os" "os"
"os/signal"
"regexp" "regexp"
"strings" "strings"
"syscall"
"time" "time"
) )
@ -29,7 +25,7 @@ var confPath string
var address string var address string
var intReg = regexp.MustCompile(`^\d`) var intReg = regexp.MustCompile(`^\d`)
func init() { func inits() {
flag.StringVar(&confPath, "c", "config.yaml", "config file support json,yaml or url") flag.StringVar(&confPath, "c", "config.yaml", "config file support json,yaml or url")
flag.StringVar(&address, "p", "", "listen address and port") flag.StringVar(&address, "p", "", "listen address and port")
flag.Parse() flag.Parse()
@ -89,54 +85,16 @@ func cronClearCache() {
} }
} }
func flushCache() {
defer func() {
if r := recover(); r != nil {
err := mail.SendMail([]string{config.GetConfig().Mail.User}, "清空缓存失败", fmt.Sprintf("err:[%s]", r))
logs.IfError(err, "发邮件失败")
}
}()
cachemanager.Flush()
log.Println("all cache flushed")
}
func reloads() {
defer func() {
if r := recover(); r != nil {
log.Println(r)
}
}()
err := config.InitConfig(confPath)
logs.IfError(err, "获取配置文件失败", confPath)
err = logs.InitLogger()
logs.IfError(err, "日志配置错误")
_, err = db.InitDb()
logs.IfError(err, "重新读取db失败", config.GetConfig().Mysql)
err = wpconfig.InitOptions()
logs.IfError(err, "获取网站设置WpOption失败")
err = wpconfig.InitTerms()
logs.IfError(err, "获取WpTerms表失败")
wphandle.LoadPlugins()
reload.Reload()
flushCache()
log.Println("reload complete")
}
func signalNotify() {
c := make(chan os.Signal)
signal.Notify(c, syscall.SIGUSR1, syscall.SIGUSR2)
for {
switch <-c {
case syscall.SIGUSR1:
go reloads()
case syscall.SIGUSR2:
go flushCache()
}
}
}
func main() { func main() {
go signalNotify() defer func() {
if r := recover(); r != nil {
fmt.Println(r)
os.Exit(1)
}
}()
inits()
ossigns.SetConfPath(confPath)
go ossigns.SignalNotify()
Gin := route.SetupRouter() Gin := route.SetupRouter()
c := config.GetConfig() c := config.GetConfig()
if c.Ssl.Key != "" && c.Ssl.Cert != "" { if c.Ssl.Key != "" && c.Ssl.Cert != "" {

View File

@ -1,183 +0,0 @@
package reload
import (
"github.com/fthvgb1/wp-go/helper/number"
"github.com/fthvgb1/wp-go/safety"
"sync"
)
var calls []func()
var anyMap = safety.NewMap[string, any]()
type safetyVar[T, A any] struct {
Val *safety.Var[val[T]]
mutex sync.Mutex
}
type val[T any] struct {
v T
ok bool
counter number.Counter[int]
}
type safetyMap[K comparable, V, A any] struct {
val *safety.Map[K, V]
mutex sync.Mutex
}
var safetyMaps = safety.NewMap[string, any]()
var safetyMapLock = sync.Mutex{}
func GetAnyMapFnBys[K comparable, V, A any](namespace string, fn func(A) V) func(key K, args A) V {
m := safetyMapFn[K, V, A](namespace)
return func(key K, a A) V {
v, ok := m.val.Load(key)
if ok {
return v
}
m.mutex.Lock()
v, ok = m.val.Load(key)
if ok {
m.mutex.Unlock()
return v
}
v = fn(a)
m.val.Store(key, v)
m.mutex.Unlock()
return v
}
}
func safetyMapFn[K comparable, V, A any](namespace string) *safetyMap[K, V, A] {
vv, ok := safetyMaps.Load(namespace)
var m *safetyMap[K, V, A]
if ok {
m = vv.(*safetyMap[K, V, A])
} else {
safetyMapLock.Lock()
vv, ok = safetyMaps.Load(namespace)
if ok {
m = vv.(*safetyMap[K, V, A])
} else {
m = &safetyMap[K, V, A]{safety.NewMap[K, V](), sync.Mutex{}}
Push(func() {
m.val.Flush()
})
safetyMaps.Store(namespace, m)
}
safetyMapLock.Unlock()
}
return m
}
func GetAnyValMapBy[K comparable, V, A any](namespace string, key K, a A, fn func(A) V) V {
m := safetyMapFn[K, V, A](namespace)
v, ok := m.val.Load(key)
if ok {
return v
}
m.mutex.Lock()
v, ok = m.val.Load(key)
if ok {
m.mutex.Unlock()
return v
}
v = fn(a)
m.val.Store(key, v)
m.mutex.Unlock()
return v
}
func anyVal[T, A any](namespace string, counter bool) *safetyVar[T, A] {
var vv *safetyVar[T, A]
vvv, ok := safetyMaps.Load(namespace)
if ok {
vv = vvv.(*safetyVar[T, A])
} else {
safetyMapLock.Lock()
vvv, ok = safetyMaps.Load(namespace)
if ok {
vv = vvv.(*safetyVar[T, A])
} else {
v := val[T]{}
if counter {
v.counter = number.Counters[int]()
}
vv = &safetyVar[T, A]{safety.NewVar(v), sync.Mutex{}}
Push(func() {
vv.Val.Flush()
})
safetyMaps.Store(namespace, vv)
}
safetyMapLock.Unlock()
}
return vv
}
func GetAnyValBy[T, A any](namespace string, tryTimes int, a A, fn func(A) (T, bool)) T {
var vv = anyVal[T, A](namespace, true)
var ok bool
v := vv.Val.Load()
if v.ok {
return v.v
}
vv.mutex.Lock()
v = vv.Val.Load()
if v.ok {
vv.mutex.Unlock()
return v.v
}
v.v, ok = fn(a)
times := v.counter()
if ok || times >= tryTimes {
v.ok = true
vv.Val.Store(v)
}
vv.mutex.Unlock()
return v.v
}
func GetAnyValBys[T, A any](namespace string, a A, fn func(A) T) T {
var vv = anyVal[T, A](namespace, false)
v := vv.Val.Load()
if v.ok {
return v.v
}
vv.mutex.Lock()
v = vv.Val.Load()
if v.ok {
vv.mutex.Unlock()
return v.v
}
v.v = fn(a)
v.ok = true
vv.Val.Store(v)
vv.mutex.Unlock()
return v.v
}
func Vars[T any](defaults T) *safety.Var[T] {
ss := safety.NewVar(defaults)
calls = append(calls, func() {
ss.Store(defaults)
})
return ss
}
func VarsBy[T any](fn func() T) *safety.Var[T] {
ss := safety.NewVar(fn())
calls = append(calls, func() {
ss.Store(fn())
})
return ss
}
func Push(fn ...func()) {
calls = append(calls, fn...)
}
func Reload() {
for _, call := range calls {
call()
}
anyMap.Flush()
safetyMaps.Flush()
}

View File

@ -1,97 +0,0 @@
package route
import (
"github.com/fthvgb1/wp-go/app/actions"
"github.com/fthvgb1/wp-go/app/cmd/reload"
"github.com/fthvgb1/wp-go/app/middleware"
"github.com/fthvgb1/wp-go/app/pkg/config"
"github.com/fthvgb1/wp-go/app/pkg/constraints"
"github.com/fthvgb1/wp-go/app/theme"
"github.com/fthvgb1/wp-go/app/wpconfig"
str "github.com/fthvgb1/wp-go/helper/strings"
"github.com/gin-contrib/gzip"
"github.com/gin-contrib/pprof"
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin"
)
var hooker []func(r *gin.Engine)
// Hook 方便插件在init时使用
func Hook(fn ...func(r *gin.Engine)) {
hooker = append(hooker, fn...)
}
func SetupRouter() *gin.Engine {
// Disable Console Color
// gin.DisableConsoleColor()
r := gin.New()
c := config.GetConfig()
if len(c.TrustIps) > 0 {
err := r.SetTrustedProxies(c.TrustIps)
if err != nil {
panic(err)
}
}
r.HTMLRender = theme.Template()
wpconfig.SetTemplateFs(theme.TemplateFs)
siteFlowLimitMiddleware, siteFlow := middleware.FlowLimit(c.MaxRequestSleepNum, c.MaxRequestNum, c.CacheTime.SleepTime)
r.Use(
gin.Logger(),
middleware.ValidateServerNames(),
middleware.RecoverAndSendMail(gin.DefaultErrorWriter),
siteFlowLimitMiddleware,
middleware.SetStaticFileCache,
)
//gzip 因为一般会用nginx做反代时自动使用gzip,所以go这边本身可以不用
if c.Gzip {
r.Use(gzip.Gzip(gzip.DefaultCompression, gzip.WithExcludedPaths([]string{
"/wp-includes/", "/wp-content/",
})))
}
if c.WpDir == "" {
panic("wordpress path can't be empty")
}
r.Static("/wp-content/uploads", str.Join(c.WpDir, "/wp-content/uploads"))
r.Static("/wp-content/themes", str.Join(c.WpDir, "/wp-content/themes"))
r.Static("/wp-content/plugins", str.Join(c.WpDir, "/wp-content/plugins"))
r.Static("/wp-includes/css", str.Join(c.WpDir, "/wp-includes/css"))
r.Static("/wp-includes/fonts", str.Join(c.WpDir, "/wp-includes/fonts"))
r.Static("/wp-includes/js", str.Join(c.WpDir, "/wp-includes/js"))
store := cookie.NewStore([]byte("secret"))
r.Use(sessions.Sessions("go-wp", store))
r.GET("/", middleware.SearchLimit(c.SingleIpSearchNum), actions.ThemeHook(constraints.Home))
r.GET("/page/:page", actions.ThemeHook(constraints.Home))
r.GET("/p/category/:category", actions.ThemeHook(constraints.Category))
r.GET("/p/category/:category/page/:page", actions.ThemeHook(constraints.Category))
r.GET("/p/tag/:tag", actions.ThemeHook(constraints.Tag))
r.GET("/p/tag/:tag/page/:page", actions.ThemeHook(constraints.Tag))
r.GET("/p/date/:year/:month", actions.ThemeHook(constraints.Archive))
r.GET("/p/date/:year/:month/page/:page", actions.ThemeHook(constraints.Archive))
r.GET("/p/author/:author", actions.ThemeHook(constraints.Author))
r.GET("/p/author/:author/page/:page", actions.ThemeHook(constraints.Author))
r.POST("/login", actions.Login)
r.GET("/p/:id", actions.ThemeHook(constraints.Detail))
r.GET("/p/:id/feed", actions.PostFeed)
r.GET("/feed", actions.Feed)
r.GET("/comments/feed", actions.CommentsFeed)
//r.NoRoute(actions.ThemeHook(constraints.NoRoute))
commentMiddleWare, _ := middleware.FlowLimit(c.MaxRequestSleepNum, 5, c.CacheTime.SleepTime)
r.POST("/comment", commentMiddleWare, actions.PostComment)
if c.Pprof != "" {
pprof.Register(r, c.Pprof)
}
for _, fn := range hooker {
fn(r)
}
reload.Push(func() {
c := config.GetConfig()
siteFlow(c.MaxRequestSleepNum, c.MaxRequestNum, c.CacheTime.SleepTime)
})
return r
}

View File

@ -4,7 +4,7 @@ import (
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"github.com/fthvgb1/wp-go/app/pkg/config" "github.com/fthvgb1/wp-go/app/pkg/config"
"github.com/soxfmr/gomail" "gopkg.in/gomail.v2"
"mime" "mime"
"path" "path"
) )

View File

@ -19,9 +19,9 @@ func FlowLimit(maxRequestSleepNum, maxRequestNum int64, sleepTime []time.Duratio
} }
s := safety.Var[[]time.Duration]{} s := safety.Var[[]time.Duration]{}
s.Store(sleepTime) s.Store(sleepTime)
fn := func(msn, mn int64, st []time.Duration) { fn := func(sleepNum, maxNum int64, st []time.Duration) {
atomic.StoreInt64(&maxRequestSleepNum, msn) atomic.StoreInt64(&maxRequestSleepNum, sleepNum)
atomic.StoreInt64(&maxRequestNum, mn) atomic.StoreInt64(&maxRequestNum, maxNum)
s.Store(st) s.Store(st)
} }
return func(c *gin.Context) { return func(c *gin.Context) {

View File

@ -25,14 +25,25 @@ func IpLimit(num int64) (func(ctx *gin.Context), func(int64)) {
fn(num) fn(num)
return func(c *gin.Context) { return func(c *gin.Context) {
if atomic.LoadInt64(m.limitNum) <= 0 {
c.Next()
return
}
ip := c.ClientIP() ip := c.ClientIP()
s := false
m.mux.RLock() m.mux.RLock()
i, ok := m.m[ip] i, ok := m.m[ip]
m.mux.RUnlock() m.mux.RUnlock()
if !ok {
m.mux.Lock()
i = new(int64)
m.m[ip] = i
m.mux.Unlock()
}
defer func() { defer func() {
ii := atomic.LoadInt64(i) ii := atomic.LoadInt64(i)
if s && ii > 0 { if ii > 0 {
atomic.AddInt64(i, -1) atomic.AddInt64(i, -1)
if atomic.LoadInt64(i) == 0 { if atomic.LoadInt64(i) == 0 {
m.mux.Lock() m.mux.Lock()
@ -42,20 +53,12 @@ func IpLimit(num int64) (func(ctx *gin.Context), func(int64)) {
} }
}() }()
if !ok { if atomic.LoadInt64(i) >= atomic.LoadInt64(m.limitNum) {
m.mux.Lock()
i = new(int64)
m.m[ip] = i
m.mux.Unlock()
}
if atomic.LoadInt64(m.limitNum) > 0 && atomic.LoadInt64(i) >= atomic.LoadInt64(m.limitNum) {
c.Status(http.StatusForbidden) c.Status(http.StatusForbidden)
c.Abort() c.Abort()
return return
} }
atomic.AddInt64(i, 1) atomic.AddInt64(i, 1)
s = true
c.Next() c.Next()
}, fn }, fn
} }

View File

@ -1,16 +1,16 @@
package middleware package middleware
import ( import (
"github.com/fthvgb1/wp-go/app/cmd/reload"
"github.com/fthvgb1/wp-go/app/pkg/config" "github.com/fthvgb1/wp-go/app/pkg/config"
"github.com/fthvgb1/wp-go/cache/reload"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
func SearchLimit(num int64) func(ctx *gin.Context) { func SearchLimit(num int64) func(ctx *gin.Context) {
fn, reFn := IpLimit(num) fn, reFn := IpLimit(num)
reload.Push(func() { reload.Append(func() {
reFn(config.GetConfig().SingleIpSearchNum) reFn(config.GetConfig().SingleIpSearchNum)
}) }, "search-ip-limit-number")
return func(c *gin.Context) { return func(c *gin.Context) {
if c.Query("s") != "" { if c.Query("s") != "" {
fn(c) fn(c)

View File

@ -1,8 +1,8 @@
package middleware package middleware
import ( import (
"github.com/fthvgb1/wp-go/app/cmd/reload"
"github.com/fthvgb1/wp-go/app/pkg/config" "github.com/fthvgb1/wp-go/app/pkg/config"
"github.com/fthvgb1/wp-go/cache/reload"
"github.com/fthvgb1/wp-go/helper/maps" "github.com/fthvgb1/wp-go/helper/maps"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"net/http" "net/http"
@ -19,7 +19,7 @@ func ValidateServerNames() func(ctx *gin.Context) {
} }
} }
return m return m
}) }, "site-names")
return func(c *gin.Context) { return func(c *gin.Context) {
m := sites.Load() m := sites.Load()

69
app/ossigns/signs.go Normal file
View File

@ -0,0 +1,69 @@
package ossigns
import (
"fmt"
"github.com/fthvgb1/wp-go/app/mail"
"github.com/fthvgb1/wp-go/app/pkg/config"
"github.com/fthvgb1/wp-go/app/pkg/db"
"github.com/fthvgb1/wp-go/app/pkg/logs"
"github.com/fthvgb1/wp-go/app/plugins/wphandle"
"github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/cache/cachemanager"
"github.com/fthvgb1/wp-go/cache/reload"
"github.com/fthvgb1/wp-go/signs"
"log"
"syscall"
)
var confPath string
func SetConfPath(path string) {
confPath = path
}
func FlushCache() {
defer func() {
if r := recover(); r != nil {
err := mail.SendMail([]string{config.GetConfig().Mail.User}, "清空缓存失败", fmt.Sprintf("err:[%s]", r))
logs.IfError(err, "发邮件失败")
}
}()
cachemanager.Flush()
log.Println("all cache flushed")
}
func Reloads() {
defer func() {
if r := recover(); r != nil {
log.Println(r)
}
}()
err := config.InitConfig(confPath)
logs.IfError(err, "获取配置文件失败", confPath)
err = logs.InitLogger()
logs.IfError(err, "日志配置错误")
_, err = db.InitDb()
logs.IfError(err, "重新读取db失败", config.GetConfig().Mysql)
err = wpconfig.InitOptions()
logs.IfError(err, "获取网站设置WpOption失败")
err = wpconfig.InitTerms()
logs.IfError(err, "获取WpTerms表失败")
wphandle.LoadPlugins()
reload.Reloads("themeArgAndConfig")
FlushCache()
log.Println("reload complete")
}
func SignalNotify() {
rel := func() bool {
go Reloads()
return true
}
flu := func() bool {
go FlushCache()
return true
}
signs.Install(syscall.SIGUSR1, rel, "reload")
signs.Install(syscall.SIGUSR2, flu, "flush")
signs.Wait()
}

148
app/pkg/cache/cache.go vendored
View File

@ -2,85 +2,96 @@ package cache
import ( import (
"context" "context"
"github.com/fthvgb1/wp-go/app/cmd/cachemanager"
"github.com/fthvgb1/wp-go/app/pkg/config" "github.com/fthvgb1/wp-go/app/pkg/config"
"github.com/fthvgb1/wp-go/app/pkg/dao" "github.com/fthvgb1/wp-go/app/pkg/dao"
"github.com/fthvgb1/wp-go/app/pkg/logs" "github.com/fthvgb1/wp-go/app/pkg/logs"
"github.com/fthvgb1/wp-go/app/pkg/models" "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/slice" "github.com/fthvgb1/wp-go/cache/reload"
"github.com/fthvgb1/wp-go/safety"
"time" "time"
) )
var postContextCache *cache.MapCache[uint64, dao.PostContext]
var categoryAndTagsCaches *cache.MapCache[string, []models.TermsMy]
var recentPostsCaches *cache.VarCache[[]models.Posts]
var recentCommentsCaches *cache.VarCache[[]models.Comments]
var postCommentCaches *cache.MapCache[uint64, []uint64]
var postsCache *cache.MapCache[uint64, models.Posts]
var postMetaCache *cache.MapCache[uint64, map[string]any]
var monthPostsCache *cache.MapCache[string, []uint64]
var postListIdsCache *cache.MapCache[string, dao.PostIds]
var searchPostIdsCache *cache.MapCache[string, dao.PostIds]
var maxPostIdCache *cache.VarCache[uint64]
var usersCache *cache.MapCache[uint64, models.Users]
var usersNameCache *cache.MapCache[string, models.Users]
var commentsCache *cache.MapCache[uint64, models.Comments]
var feedCache *cache.VarCache[[]string]
var postFeedCache *cache.MapCache[string, string]
var commentsFeedCache *cache.VarCache[[]string]
var newCommentCache *cache.MapCache[string, string]
var allUsernameCache *cache.VarCache[map[string]struct{}]
func InitActionsCommonCache() { func InitActionsCommonCache() {
c := config.GetConfig() c := config.GetConfig()
searchPostIdsCache = cachemanager.MapCacheBy[string](dao.SearchPostIds, c.CacheTime.SearchPostCacheTime) cachemanager.NewMemoryMapCache(nil, dao.SearchPostIds, c.CacheTime.SearchPostCacheTime, "searchPostIds", func() time.Duration {
return config.GetConfig().CacheTime.SearchPostCacheTime
})
postListIdsCache = cachemanager.MapCacheBy[string](dao.SearchPostIds, c.CacheTime.PostListCacheTime) cachemanager.NewMemoryMapCache(nil, dao.SearchPostIds, c.CacheTime.PostListCacheTime, "listPostIds", func() time.Duration {
return config.GetConfig().CacheTime.PostListCacheTime
})
monthPostsCache = cachemanager.MapCacheBy[string](dao.MonthPost, c.CacheTime.MonthPostCacheTime) cachemanager.NewMemoryMapCache(nil, dao.MonthPost, c.CacheTime.MonthPostCacheTime, "monthPostIds", func() time.Duration {
return config.GetConfig().CacheTime.MonthPostCacheTime
})
postContextCache = cachemanager.MapCacheBy[uint64](dao.GetPostContext, c.CacheTime.ContextPostCacheTime) cachemanager.NewMemoryMapCache(nil, dao.GetPostContext, c.CacheTime.ContextPostCacheTime, "postContext", func() time.Duration {
return config.GetConfig().CacheTime.ContextPostCacheTime
})
postsCache = cachemanager.MapBatchCacheBy(dao.GetPostsByIds, c.CacheTime.PostDataCacheTime) cachemanager.NewMemoryMapCache(dao.GetPostsByIds, nil, c.CacheTime.PostDataCacheTime, "postData", func() time.Duration {
return config.GetConfig().CacheTime.PostDataCacheTime
})
postMetaCache = cachemanager.MapBatchCacheBy(dao.GetPostMetaByPostIds, c.CacheTime.PostDataCacheTime) cachemanager.NewMemoryMapCache(dao.GetPostMetaByPostIds, nil, c.CacheTime.PostDataCacheTime, "postMetaData", func() time.Duration {
return config.GetConfig().CacheTime.PostDataCacheTime
})
categoryAndTagsCaches = cachemanager.MapCacheBy[string](dao.CategoriesAndTags, c.CacheTime.CategoryCacheTime) cachemanager.NewMemoryMapCache(nil, dao.CategoriesAndTags, c.CacheTime.CategoryCacheTime, "categoryAndTagsData", func() time.Duration {
return config.GetConfig().CacheTime.CategoryCacheTime
})
recentPostsCaches = cache.NewVarCache(dao.RecentPosts, c.CacheTime.RecentPostCacheTime) cachemanager.NewVarMemoryCache(dao.RecentPosts, c.CacheTime.RecentPostCacheTime, "recentPosts", func() time.Duration {
return config.GetConfig().CacheTime.RecentPostCacheTime
})
recentCommentsCaches = cache.NewVarCache(dao.RecentComments, c.CacheTime.RecentCommentsCacheTime) cachemanager.NewVarMemoryCache(RecentComment, c.CacheTime.RecentCommentsCacheTime, "recentComments", func() time.Duration {
return config.GetConfig().CacheTime.RecentCommentsCacheTime
})
postCommentCaches = cachemanager.MapCacheBy[uint64](dao.PostComments, c.CacheTime.PostCommentsCacheTime) cachemanager.NewMemoryMapCache(nil, dao.CommentNum, 30*time.Second, "commentNumber", func() time.Duration {
return config.GetConfig().CacheTime.CommentsIncreaseUpdateTime
})
maxPostIdCache = cache.NewVarCache(dao.GetMaxPostId, c.CacheTime.MaxPostIdCacheTime) cachemanager.NewMemoryMapCache(nil, PostTopComments, 30*time.Second, "PostCommentsIds", func() time.Duration {
return config.GetConfig().CacheTime.CommentsIncreaseUpdateTime
})
cachemanager.NewMemoryMapCache(nil, dao.PostTopCommentNum, 30*time.Second, "postTopCommentsNum", func() time.Duration {
return config.GetConfig().CacheTime.CommentsIncreaseUpdateTime
})
usersCache = cachemanager.MapCacheBy[uint64](dao.GetUserById, c.CacheTime.UserInfoCacheTime) cachemanager.NewMemoryMapCache(dao.GetCommentByIds, nil, time.Hour, "postCommentData", func() time.Duration {
return config.GetConfig().CacheTime.CommentsCacheTime
})
usersNameCache = cachemanager.MapCacheBy[string](dao.GetUserByName, c.CacheTime.UserInfoCacheTime) cachemanager.NewMemoryMapCache(dao.CommentChildren, nil, time.Minute, "commentChildren", func() time.Duration {
return config.GetConfig().CacheTime.CommentsIncreaseUpdateTime
})
commentsCache = cachemanager.MapBatchCacheBy(dao.GetCommentByIds, c.CacheTime.CommentsCacheTime) cachemanager.NewVarMemoryCache(dao.GetMaxPostId, c.CacheTime.MaxPostIdCacheTime, "maxPostId", func() time.Duration {
return config.GetConfig().CacheTime.MaxPostIdCacheTime
})
allUsernameCache = cache.NewVarCache(dao.AllUsername, c.CacheTime.UserInfoCacheTime) cachemanager.NewMemoryMapCache(nil, dao.GetUserById, c.CacheTime.UserInfoCacheTime, "userData", func() time.Duration {
return config.GetConfig().CacheTime.UserInfoCacheTime
})
feedCache = cache.NewVarCache(feed, time.Hour) cachemanager.NewMemoryMapCache(nil, dao.GetUserByName, c.CacheTime.UserInfoCacheTime, "usernameToUserData", func() time.Duration {
return config.GetConfig().CacheTime.UserInfoCacheTime
})
postFeedCache = cachemanager.MapCacheBy[string](postFeed, time.Hour) cachemanager.NewVarMemoryCache(dao.AllUsername, c.CacheTime.UserInfoCacheTime, "allUsername", func() time.Duration {
return config.GetConfig().CacheTime.UserInfoCacheTime
})
commentsFeedCache = cache.NewVarCache(commentsFeed, time.Hour) cachemanager.NewVarMemoryCache(SiteFeed, time.Hour, "siteFeed")
newCommentCache = cachemanager.MapCacheBy[string, string](nil, 15*time.Minute) cachemanager.NewMemoryMapCache(nil, PostFeed, time.Hour, "postFeed")
cachemanager.NewVarMemoryCache(CommentsFeed, time.Hour, "commentsFeed")
cachemanager.NewMemoryMapCache[string, string](nil, nil, 15*time.Minute, "NewComment")
InitFeed() InitFeed()
} }
@ -91,19 +102,19 @@ type Arch struct {
month time.Month month time.Month
} }
var arch = safety.NewVar(Arch{ var arch = reload.Vars(Arch{
fn: dao.Archives, fn: dao.Archives,
}) }, "archives-year-month-data")
func Archives(ctx context.Context) []models.PostArchive { func Archives(ctx context.Context) []models.PostArchive {
a := arch.Load() a := arch.Load()
data := a.data data := a.data
l := len(data) l := len(data)
m := time.Now().Month() m := time.Now().Month()
if l > 0 && a.month != m || l < 1 { if l < 1 || a.month != m {
r, err := a.fn(ctx) r, err := a.fn(ctx)
if err != nil { if err != nil {
logs.Error(err, "set cache fail") logs.Error(err, "set cache Archives fail")
return nil return nil
} }
a.month = m a.month = m
@ -113,30 +124,3 @@ func Archives(ctx context.Context) []models.PostArchive {
} }
return data return data
} }
// CategoriesTags categories or tags
//
// t is constraints.Tag or constraints.Category
func CategoriesTags(ctx context.Context, t ...string) []models.TermsMy {
tt := ""
if len(t) > 0 {
tt = t[0]
}
r, err := categoryAndTagsCaches.GetCache(ctx, tt, time.Second, ctx, tt)
logs.IfError(err, "get category fail")
return r
}
func AllCategoryTagsNames(ctx context.Context, t ...string) map[string]struct{} {
tt := ""
if len(t) > 0 {
tt = t[0]
}
r, err := categoryAndTagsCaches.GetCache(ctx, tt, time.Second, ctx, tt)
if err != nil {
logs.Error(err, "get category fail")
return nil
}
return slice.ToMap(r, func(t models.TermsMy) (string, struct{}) {
return t.Name, struct{}{}
}, true)
}

39
app/pkg/cache/categoryandtag.go vendored Normal file
View File

@ -0,0 +1,39 @@
package cache
import (
"context"
"github.com/fthvgb1/wp-go/app/pkg/logs"
"github.com/fthvgb1/wp-go/app/pkg/models"
"github.com/fthvgb1/wp-go/cache/cachemanager"
"github.com/fthvgb1/wp-go/helper/slice"
"time"
)
// CategoriesTags get all categories or tags
//
// query func see dao.CategoriesAndTags
//
// t is constraints.Tag or constraints.Category
func CategoriesTags(ctx context.Context, t ...string) []models.TermsMy {
tt := ""
if len(t) > 0 {
tt = t[0]
}
r, err := cachemanager.GetBy[[]models.TermsMy]("categoryAndTagsData", ctx, tt, time.Second)
logs.IfError(err, "get category fail")
return r
}
func AllCategoryTagsNames(ctx context.Context, t ...string) map[string]struct{} {
tt := ""
if len(t) > 0 {
tt = t[0]
}
r, err := cachemanager.GetBy[[]models.TermsMy]("categoryAndTagsData", ctx, tt, time.Second)
if err != nil {
logs.Error(err, "get category fail")
return nil
}
return slice.ToMap(r, func(t models.TermsMy) (string, struct{}) {
return t.Name, struct{}{}
}, true)
}

View File

@ -2,16 +2,23 @@ package cache
import ( import (
"context" "context"
"fmt"
"github.com/fthvgb1/wp-go/app/pkg/dao"
"github.com/fthvgb1/wp-go/app/pkg/logs" "github.com/fthvgb1/wp-go/app/pkg/logs"
"github.com/fthvgb1/wp-go/app/pkg/models" "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"
"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/number"
str "github.com/fthvgb1/wp-go/helper/strings"
"time" "time"
) )
// RecentComments query func see RecentComment
func RecentComments(ctx context.Context, n int) (r []models.Comments) { func RecentComments(ctx context.Context, n int) (r []models.Comments) {
nn := number.Max(n, 10) nn := number.Max(n, 10)
r, err := recentCommentsCaches.GetCache(ctx, time.Second, ctx, nn) r, err := cachemanager.GetVarVal[[]models.Comments]("recentComments", ctx, time.Second, ctx, nn)
if len(r) > n { if len(r) > n {
r = r[0:n] r = r[0:n]
} }
@ -19,22 +26,99 @@ func RecentComments(ctx context.Context, n int) (r []models.Comments) {
return return
} }
func PostComments(ctx context.Context, Id uint64) ([]models.Comments, error) { // PostTopLevelCommentIds query func see PostTopComments
ids, err := postCommentCaches.GetCache(ctx, Id, time.Second, ctx, Id) func PostTopLevelCommentIds(ctx context.Context, postId uint64, page, limit, total int, order string, a ...any) ([]uint64, error) {
if err != nil { var key string
return nil, err if len(a) > 0 {
key = helper.ParseArgs("", a...)
} }
return GetCommentByIds(ctx, ids) if key == "" {
key = fmt.Sprintf("%d-%d-%d-%d-%s", postId, page, limit, total, order)
}
return cachemanager.GetBy[[]uint64]("PostCommentsIds", ctx,
key, time.Second, postId, page, limit, 0, order)
} }
// GetCommentById query func see dao.GetCommentByIds
func GetCommentById(ctx context.Context, id uint64) (models.Comments, error) { func GetCommentById(ctx context.Context, id uint64) (models.Comments, error) {
return commentsCache.GetCache(ctx, id, time.Second, ctx, id) return cachemanager.GetBy[models.Comments]("postCommentData", ctx, id, time.Second)
} }
func GetCommentByIds(ctx context.Context, ids []uint64) ([]models.Comments, error) { // GetCommentDataByIds query func see dao.GetCommentByIds
return commentsCache.GetCacheBatch(ctx, ids, time.Second, ctx, ids) func GetCommentDataByIds(ctx context.Context, ids []uint64) ([]models.Comments, error) {
return cachemanager.GetBatchBy[models.Comments]("postCommentData", ctx, ids, time.Second)
} }
func NewCommentCache() *cache.MapCache[string, string] { func NewCommentCache() *cache.MapCache[string, string] {
return newCommentCache r, _ := cachemanager.GetMapCache[string, string]("NewComment")
return r
}
func PostTopComments(ctx context.Context, _ string, a ...any) ([]uint64, error) {
postId := a[0].(uint64)
page := a[1].(int)
limit := a[2].(int)
total := a[3].(int)
order := helper.ParseArgs("", a...)
if order == "" {
order = wpconfig.GetOption("comment_order")
}
v, _, err := dao.PostCommentsIds(ctx, postId, page, limit, total, order)
if err != nil {
return nil, err
}
return v, nil
}
func RecentComment(ctx context.Context, a ...any) (r []models.Comments, err error) {
r, err = dao.RecentComments(ctx, a...)
if err != nil {
return r, err
}
for i, comment := range r {
r[i].CommentAuthorUrl, err = GetCommentUrl(ctx, comment.CommentId, comment.CommentPostId)
if err != nil {
return nil, err
}
}
return
}
func GetCommentUrl(ctx context.Context, commentId, postId uint64) (string, error) {
if wpconfig.GetOption("page_comments") != "1" {
return fmt.Sprintf("/p/%d#comment-%d", postId, commentId), nil
}
commentsPerPage := str.ToInteger(wpconfig.GetOption("comments_per_page"), 5)
topCommentId, err := AncestorCommentId(ctx, commentId)
if err != nil {
return "", err
}
totalNum, err := cachemanager.GetBy[int]("postTopCommentsNum", ctx, postId, time.Second)
if err != nil {
return "", err
}
if totalNum <= commentsPerPage {
return fmt.Sprintf("/p/%d#comment-%d", postId, commentId), nil
}
num, err := dao.PreviousCommentNum(ctx, topCommentId, postId)
if err != nil {
return "", err
}
order := wpconfig.GetOption("comment_order")
page := number.DivideCeil(num+1, commentsPerPage)
if order == "desc" {
page = number.DivideCeil(totalNum-num, commentsPerPage)
}
return fmt.Sprintf("/p/%d/comment-page-%d/#comment-%d", postId, page, commentId), nil
}
func AncestorCommentId(ctx context.Context, commentId uint64) (uint64, error) {
comment, err := GetCommentById(ctx, commentId)
if err != nil {
return 0, err
}
if comment.CommentParent == 0 {
return comment.CommentId, nil
}
return AncestorCommentId(ctx, comment.CommentParent)
} }

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

@ -1,6 +1,8 @@
package cache package cache
import ( import (
"context"
"errors"
"fmt" "fmt"
"github.com/fthvgb1/wp-go/app/pkg/logs" "github.com/fthvgb1/wp-go/app/pkg/logs"
"github.com/fthvgb1/wp-go/app/pkg/models" "github.com/fthvgb1/wp-go/app/pkg/models"
@ -8,11 +10,11 @@ import (
"github.com/fthvgb1/wp-go/app/plugins/wpposts" "github.com/fthvgb1/wp-go/app/plugins/wpposts"
"github.com/fthvgb1/wp-go/app/wpconfig" "github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/cache" "github.com/fthvgb1/wp-go/cache"
"github.com/fthvgb1/wp-go/cache/cachemanager"
"github.com/fthvgb1/wp-go/helper/slice" "github.com/fthvgb1/wp-go/helper/slice"
str "github.com/fthvgb1/wp-go/helper/strings" str "github.com/fthvgb1/wp-go/helper/strings"
"github.com/fthvgb1/wp-go/plugin/digest" "github.com/fthvgb1/wp-go/plugin/digest"
"github.com/fthvgb1/wp-go/rss2" "github.com/fthvgb1/wp-go/rss2"
"github.com/gin-gonic/gin"
"strings" "strings"
"time" "time"
) )
@ -33,20 +35,25 @@ func InitFeed() {
} }
} }
// CommentsFeedCache query func see CommentsFeed
func CommentsFeedCache() *cache.VarCache[[]string] { func CommentsFeedCache() *cache.VarCache[[]string] {
return commentsFeedCache r, _ := cachemanager.GetVarCache[[]string]("commentsFeed")
return r
} }
// FeedCache query func see SiteFeed
func FeedCache() *cache.VarCache[[]string] { func FeedCache() *cache.VarCache[[]string] {
return feedCache r, _ := cachemanager.GetVarCache[[]string]("siteFeed")
return r
} }
// PostFeedCache query func see PostFeed
func PostFeedCache() *cache.MapCache[string, string] { func PostFeedCache() *cache.MapCache[string, string] {
return postFeedCache r, _ := cachemanager.GetMapCache[string, string]("postFeed")
return r
} }
func feed(arg ...any) (xml []string, err error) { func SiteFeed(c context.Context, _ ...any) (xml []string, err error) {
c := arg[0].(*gin.Context)
r := RecentPosts(c, 10) r := RecentPosts(c, 10)
ids := slice.Map(r, func(t models.Posts) uint64 { ids := slice.Map(r, func(t models.Posts) uint64 {
return t.Id return t.Id
@ -92,9 +99,7 @@ func feed(arg ...any) (xml []string, err error) {
return return
} }
func postFeed(arg ...any) (x string, err error) { func PostFeed(c context.Context, id string, _ ...any) (x string, err error) {
c := arg[0].(*gin.Context)
id := arg[1].(string)
ID := str.ToInteger[uint64](id, 0) ID := str.ToInteger[uint64](id, 0)
maxId, err := GetMaxPostId(c) maxId, err := GetMaxPostId(c)
logs.IfError(err, "get max post id") logs.IfError(err, "get max post id")
@ -105,7 +110,12 @@ func postFeed(arg ...any) (x string, err error) {
if post.Id == 0 || err != nil { if post.Id == 0 || err != nil {
return return
} }
comments, err := PostComments(c, post.Id) limit := str.ToInteger(wpconfig.GetOption("comments_per_page"), 10)
ids, err := PostTopLevelCommentIds(c, ID, 1, limit, 0, "desc", "latest-comment")
if err != nil {
return
}
comments, err := GetCommentDataByIds(c, ids)
if err != nil { if err != nil {
return return
} }
@ -121,10 +131,14 @@ func postFeed(arg ...any) (x string, err error) {
wpposts.PasswdProjectContent(&post) wpposts.PasswdProjectContent(&post)
if len(comments) > 0 { if len(comments) > 0 {
t := comments[len(comments)-1] t := comments[len(comments)-1]
u, err := GetCommentUrl(c, t.CommentId, t.CommentPostId)
if err != nil {
return "", err
}
rs.Items = []rss2.Item{ rs.Items = []rss2.Item{
{ {
Title: fmt.Sprintf("评价者:%s", t.CommentAuthor), Title: fmt.Sprintf("评价者:%s", t.CommentAuthor),
Link: fmt.Sprintf("%s/p/%d#comment-%d", site, post.Id, t.CommentId), Link: fmt.Sprintf("%s%s", site, u),
Creator: t.CommentAuthor, Creator: t.CommentAuthor,
PubDate: t.CommentDateGmt.Format(timeFormat), PubDate: t.CommentDateGmt.Format(timeFormat),
Guid: fmt.Sprintf("%s#comment-%d", post.Guid, t.CommentId), Guid: fmt.Sprintf("%s#comment-%d", post.Guid, t.CommentId),
@ -135,9 +149,14 @@ func postFeed(arg ...any) (x string, err error) {
} }
} else { } else {
rs.Items = slice.Map(comments, func(t models.Comments) rss2.Item { rs.Items = slice.Map(comments, func(t models.Comments) rss2.Item {
u, er := GetCommentUrl(c, t.CommentId, t.CommentPostId)
if er != nil {
err = errors.Join(err, er)
return rss2.Item{}
}
return rss2.Item{ return rss2.Item{
Title: fmt.Sprintf("评价者:%s", t.CommentAuthor), Title: fmt.Sprintf("评价者:%s", t.CommentAuthor),
Link: fmt.Sprintf("%s/p/%d#comment-%d", site, post.Id, t.CommentId), Link: fmt.Sprintf("%s%s", site, u),
Creator: t.CommentAuthor, Creator: t.CommentAuthor,
PubDate: t.CommentDateGmt.Format(timeFormat), PubDate: t.CommentDateGmt.Format(timeFormat),
Guid: fmt.Sprintf("%s#comment-%d", post.Guid, t.CommentId), Guid: fmt.Sprintf("%s#comment-%d", post.Guid, t.CommentId),
@ -150,15 +169,14 @@ func postFeed(arg ...any) (x string, err error) {
return return
} }
func commentsFeed(args ...any) (r []string, err error) { func CommentsFeed(c context.Context, _ ...any) (r []string, err error) {
c := args[0].(*gin.Context)
commens := RecentComments(c, 10) commens := RecentComments(c, 10)
rs := templateRss rs := templateRss
rs.Title = fmt.Sprintf("\"%s\"的评论", wpconfig.GetOption("blogname")) rs.Title = fmt.Sprintf("\"%s\"的评论", wpconfig.GetOption("blogname"))
rs.LastBuildDate = time.Now().Format(timeFormat) rs.LastBuildDate = time.Now().Format(timeFormat)
site := wpconfig.GetOption("siteurl") site := wpconfig.GetOption("siteurl")
rs.AtomLink = fmt.Sprintf("%s/comments/feed", site) 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 return t.CommentId
})) }))
if nil != err { if nil != err {
@ -175,9 +193,14 @@ func commentsFeed(args ...any) (r []string, err error) {
} else { } else {
content = digest.StripTags(t.CommentContent, "") content = digest.StripTags(t.CommentContent, "")
} }
u, er := GetCommentUrl(c, t.CommentId, t.CommentPostId)
if er != nil {
errors.Join(err, er)
}
u = str.Join(site, u)
return rss2.Item{ return rss2.Item{
Title: fmt.Sprintf("%s对《%s》的评论", t.CommentAuthor, post.PostTitle), Title: fmt.Sprintf("%s对《%s》的评论", t.CommentAuthor, post.PostTitle),
Link: fmt.Sprintf("%s/p/%d#comment-%d", site, post.Id, t.CommentId), Link: u,
Creator: t.CommentAuthor, Creator: t.CommentAuthor,
Description: desc, Description: desc,
PubDate: t.CommentDateGmt.Format(timeFormat), PubDate: t.CommentDateGmt.Format(timeFormat),

View File

@ -2,14 +2,16 @@ package cache
import ( import (
"context" "context"
"github.com/fthvgb1/wp-go/cache/cachemanager"
"time" "time"
) )
func GetPostMetaByPostIds(ctx context.Context, ids []uint64) (r []map[string]any, err error) { // GetPostMetaByPostIds query func see dao.GetPostMetaByPostIds
r, err = postMetaCache.GetCacheBatch(ctx, ids, time.Second, ctx, ids) func GetPostMetaByPostIds(ctx context.Context, ids []uint64) ([]map[string]any, error) {
return return cachemanager.GetBatchBy[map[string]any]("postMetaData", ctx, ids, time.Second)
} }
func GetPostMetaByPostId(ctx context.Context, id uint64) (r map[string]any, err error) {
r, err = postMetaCache.GetCache(ctx, id, time.Second, ctx, id) // GetPostMetaByPostId query func see dao.GetPostMetaByPostIds
return func GetPostMetaByPostId(ctx context.Context, id uint64) (map[string]any, error) {
return cachemanager.GetBy[map[string]any]("postMetaData", ctx, id, time.Second)
} }

View File

@ -3,26 +3,30 @@ package cache
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/fthvgb1/wp-go/app/pkg/dao"
"github.com/fthvgb1/wp-go/app/pkg/logs" "github.com/fthvgb1/wp-go/app/pkg/logs"
"github.com/fthvgb1/wp-go/app/pkg/models" "github.com/fthvgb1/wp-go/app/pkg/models"
"github.com/fthvgb1/wp-go/app/wpconfig" "github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/cache/cachemanager"
"github.com/fthvgb1/wp-go/helper/number" "github.com/fthvgb1/wp-go/helper/number"
"github.com/fthvgb1/wp-go/helper/slice" "github.com/fthvgb1/wp-go/helper/slice"
str "github.com/fthvgb1/wp-go/helper/strings" str "github.com/fthvgb1/wp-go/helper/strings"
"github.com/gin-gonic/gin"
"time" "time"
) )
// GetPostById query func see dao.GetPostsByIds
func GetPostById(ctx context.Context, id uint64) (models.Posts, error) { func GetPostById(ctx context.Context, id uint64) (models.Posts, error) {
return postsCache.GetCache(ctx, id, time.Second, ctx, id) return cachemanager.GetBy[models.Posts]("postData", ctx, id, time.Second)
} }
// GetPostsByIds query func see dao.GetPostsByIds
func GetPostsByIds(ctx context.Context, ids []uint64) ([]models.Posts, error) { func GetPostsByIds(ctx context.Context, ids []uint64) ([]models.Posts, error) {
return postsCache.GetCacheBatch(ctx, ids, time.Second, ctx, ids) return cachemanager.GetBatchBy[models.Posts]("postData", ctx, ids, time.Second)
} }
// SearchPost query func see dao.SearchPostIds
func SearchPost(ctx context.Context, key string, args ...any) (r []models.Posts, total int, err error) { func SearchPost(ctx context.Context, key string, args ...any) (r []models.Posts, total int, err error) {
ids, err := searchPostIdsCache.GetCache(ctx, key, time.Second, args...) ids, err := cachemanager.GetBy[dao.PostIds]("searchPostIds", ctx, key, time.Second, args...)
if err != nil { if err != nil {
return return
} }
@ -31,8 +35,9 @@ func SearchPost(ctx context.Context, key string, args ...any) (r []models.Posts,
return return
} }
// PostLists query func see dao.SearchPostIds
func PostLists(ctx context.Context, key string, args ...any) (r []models.Posts, total int, err error) { func PostLists(ctx context.Context, key string, args ...any) (r []models.Posts, total int, err error) {
ids, err := postListIdsCache.GetCache(ctx, key, time.Second, args...) ids, err := cachemanager.GetBy[dao.PostIds]("listPostIds", ctx, key, time.Second, args...)
if err != nil { if err != nil {
return return
} }
@ -41,15 +46,17 @@ func PostLists(ctx context.Context, key string, args ...any) (r []models.Posts,
return return
} }
func GetMaxPostId(ctx *gin.Context) (uint64, error) { // GetMaxPostId query func see dao.GetMaxPostId
return maxPostIdCache.GetCache(ctx, time.Second, ctx) func GetMaxPostId(ctx context.Context) (uint64, error) {
return cachemanager.GetVarVal[uint64]("maxPostId", ctx, time.Second)
} }
// RecentPosts query func see dao.RecentPosts
func RecentPosts(ctx context.Context, n int) (r []models.Posts) { func RecentPosts(ctx context.Context, n int) (r []models.Posts) {
nn := n nn := n
feedNum := str.ToInteger(wpconfig.GetOption("posts_per_rss"), 10) feedNum := str.ToInteger(wpconfig.GetOption("posts_per_rss"), 10)
nn = number.Max(n, feedNum) nn = number.Max(n, feedNum)
r, err := recentPostsCaches.GetCache(ctx, time.Second, ctx, nn) r, err := cachemanager.GetVarVal[[]models.Posts]("recentPosts", ctx, time.Second, nn)
if n < len(r) { if n < len(r) {
r = r[:n] r = r[:n]
} }
@ -57,8 +64,9 @@ func RecentPosts(ctx context.Context, n int) (r []models.Posts) {
return return
} }
// GetContextPost query func see dao.GetPostContext
func GetContextPost(ctx context.Context, id uint64, date time.Time) (prev, next models.Posts, err error) { func GetContextPost(ctx context.Context, id uint64, date time.Time) (prev, next models.Posts, err error) {
postCtx, err := postContextCache.GetCache(ctx, id, time.Second, ctx, date) postCtx, err := cachemanager.GetBy[dao.PostContext]("postContext", ctx, id, time.Second, date)
if err != nil { if err != nil {
return models.Posts{}, models.Posts{}, err return models.Posts{}, models.Posts{}, err
} }
@ -67,8 +75,9 @@ func GetContextPost(ctx context.Context, id uint64, date time.Time) (prev, next
return return
} }
// GetMonthPostIds query func see dao.MonthPost
func GetMonthPostIds(ctx context.Context, year, month string, page, limit int, order string) (r []models.Posts, total int, err error) { func GetMonthPostIds(ctx context.Context, year, month string, page, limit int, order string) (r []models.Posts, total int, err error) {
res, err := monthPostsCache.GetCache(ctx, fmt.Sprintf("%s%s", year, month), time.Second, ctx, year, month) res, err := cachemanager.GetBy[[]uint64]("monthPostIds", ctx, fmt.Sprintf("%s%s", year, month), time.Second, year, month)
if err != nil { if err != nil {
return return
} }

View File

@ -4,27 +4,23 @@ import (
"context" "context"
"github.com/fthvgb1/wp-go/app/pkg/logs" "github.com/fthvgb1/wp-go/app/pkg/logs"
"github.com/fthvgb1/wp-go/app/pkg/models" "github.com/fthvgb1/wp-go/app/pkg/models"
"github.com/fthvgb1/wp-go/model" "github.com/fthvgb1/wp-go/cache/cachemanager"
"time" "time"
) )
func getUserById(a ...any) (r models.Users, err error) { // GetUserByName query func see dao.GetUserByName
ctx := a[0].(context.Context)
uid := a[1].(uint64)
r, err = model.FindOneById[models.Users](ctx, uid)
return
}
func GetUserByName(ctx context.Context, username string) (models.Users, error) { func GetUserByName(ctx context.Context, username string) (models.Users, error) {
return usersNameCache.GetCache(ctx, username, time.Second, ctx, username) return cachemanager.GetBy[models.Users]("usernameToUserData", ctx, username, time.Second)
} }
func GetAllUsername(ctx context.Context) (map[string]struct{}, error) { // GetAllUsername query func see dao.AllUsername
return allUsernameCache.GetCache(ctx, time.Second, ctx) func GetAllUsername(ctx context.Context) (map[string]uint64, error) {
return cachemanager.GetVarVal[map[string]uint64]("allUsername", ctx, time.Second)
} }
// GetUserById query func see dao.GetUserById
func GetUserById(ctx context.Context, uid uint64) models.Users { func GetUserById(ctx context.Context, uid uint64) models.Users {
r, err := usersCache.GetCache(ctx, uid, time.Second, ctx, uid) r, err := cachemanager.GetBy[models.Users]("userData", ctx, uid, time.Second)
logs.IfError(err, "get user", uid) logs.IfError(err, "get user", uid)
return r return r
} }

View File

@ -46,23 +46,24 @@ type Config struct {
} }
type CacheTime struct { type CacheTime struct {
CacheControl time.Duration `yaml:"cacheControl" json:"cacheControl,omitempty"` CacheControl time.Duration `yaml:"cacheControl" json:"cacheControl,omitempty"`
RecentPostCacheTime time.Duration `yaml:"recentPostCacheTime" json:"recentPostCacheTime,omitempty"` RecentPostCacheTime time.Duration `yaml:"recentPostCacheTime" json:"recentPostCacheTime,omitempty"`
CategoryCacheTime time.Duration `yaml:"categoryCacheTime" json:"categoryCacheTime,omitempty"` CategoryCacheTime time.Duration `yaml:"categoryCacheTime" json:"categoryCacheTime,omitempty"`
ArchiveCacheTime time.Duration `yaml:"archiveCacheTime" json:"archiveCacheTime,omitempty"` ArchiveCacheTime time.Duration `yaml:"archiveCacheTime" json:"archiveCacheTime,omitempty"`
ContextPostCacheTime time.Duration `yaml:"contextPostCacheTime" json:"contextPostCacheTime,omitempty"` ContextPostCacheTime time.Duration `yaml:"contextPostCacheTime" json:"contextPostCacheTime,omitempty"`
RecentCommentsCacheTime time.Duration `yaml:"recentCommentsCacheTime" json:"recentCommentsCacheTime,omitempty"` RecentCommentsCacheTime time.Duration `yaml:"recentCommentsCacheTime" json:"recentCommentsCacheTime,omitempty"`
DigestCacheTime time.Duration `yaml:"digestCacheTime" json:"digestCacheTime,omitempty"` DigestCacheTime time.Duration `yaml:"digestCacheTime" json:"digestCacheTime,omitempty"`
PostListCacheTime time.Duration `yaml:"postListCacheTime" json:"postListCacheTime,omitempty"` PostListCacheTime time.Duration `yaml:"postListCacheTime" json:"postListCacheTime,omitempty"`
SearchPostCacheTime time.Duration `yaml:"searchPostCacheTime" json:"searchPostCacheTime,omitempty"` SearchPostCacheTime time.Duration `yaml:"searchPostCacheTime" json:"searchPostCacheTime,omitempty"`
MonthPostCacheTime time.Duration `yaml:"monthPostCacheTime" json:"monthPostCacheTime,omitempty"` MonthPostCacheTime time.Duration `yaml:"monthPostCacheTime" json:"monthPostCacheTime,omitempty"`
PostDataCacheTime time.Duration `yaml:"postDataCacheTime" json:"postDataCacheTime,omitempty"` PostDataCacheTime time.Duration `yaml:"postDataCacheTime" json:"postDataCacheTime,omitempty"`
PostCommentsCacheTime time.Duration `yaml:"postCommentsCacheTime" json:"postCommentsCacheTime,omitempty"` PostCommentsCacheTime time.Duration `yaml:"postCommentsCacheTime" json:"postCommentsCacheTime,omitempty"`
CrontabClearCacheTime time.Duration `yaml:"crontabClearCacheTime" json:"crontabClearCacheTime,omitempty"` CrontabClearCacheTime time.Duration `yaml:"crontabClearCacheTime" json:"crontabClearCacheTime,omitempty"`
MaxPostIdCacheTime time.Duration `yaml:"maxPostIdCacheTime" json:"maxPostIdCacheTime,omitempty"` MaxPostIdCacheTime time.Duration `yaml:"maxPostIdCacheTime" json:"maxPostIdCacheTime,omitempty"`
UserInfoCacheTime time.Duration `yaml:"userInfoCacheTime" json:"userInfoCacheTime,omitempty"` UserInfoCacheTime time.Duration `yaml:"userInfoCacheTime" json:"userInfoCacheTime,omitempty"`
CommentsCacheTime time.Duration `yaml:"commentsCacheTime" json:"commentsCacheTime,omitempty"` CommentsCacheTime time.Duration `yaml:"commentsCacheTime" json:"commentsCacheTime,omitempty"`
SleepTime []time.Duration `yaml:"sleepTime" json:"sleepTime,omitempty"` SleepTime []time.Duration `yaml:"sleepTime" json:"sleepTime,omitempty"`
CommentsIncreaseUpdateTime time.Duration `yaml:"commentsIncreaseUpdateTime" json:"commentsIncreaseUpdateTime"`
} }
type Ssl struct { type Ssl struct {
@ -84,6 +85,14 @@ type Mysql struct {
Pool Pool `yaml:"pool" json:"pool"` Pool Pool `yaml:"pool" json:"pool"`
} }
func GetCustomizedConfig[T any]() (T, error) {
var r T
err := yaml.Unmarshal(fileData.Load(), &r)
return r, err
}
var fileData = safety.NewVar([]byte{})
func InitConfig(conf string) error { func InitConfig(conf string) error {
if conf == "" { if conf == "" {
conf = "config.yaml" conf = "config.yaml"
@ -95,6 +104,7 @@ func InitConfig(conf string) error {
if err != nil { if err != nil {
return err return err
} }
defer get.Body.Close()
file, err = io.ReadAll(get.Body) file, err = io.ReadAll(get.Body)
} else { } else {
file, err = os.ReadFile(conf) file, err = os.ReadFile(conf)
@ -102,6 +112,7 @@ func InitConfig(conf string) error {
if err != nil { if err != nil {
return err return err
} }
fileData.Store(file)
var c Config var c Config
err = yaml.Unmarshal(file, &c) err = yaml.Unmarshal(file, &c)
if err != nil { if err != nil {

View File

@ -2,17 +2,21 @@ package dao
import ( import (
"context" "context"
"database/sql"
"errors"
"github.com/fthvgb1/wp-go/app/pkg/models" "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/number"
"github.com/fthvgb1/wp-go/helper/slice" "github.com/fthvgb1/wp-go/helper/slice"
str "github.com/fthvgb1/wp-go/helper/strings"
"github.com/fthvgb1/wp-go/model" "github.com/fthvgb1/wp-go/model"
) )
// RecentComments // RecentComments
// param context.Context // param context.Context
func RecentComments(a ...any) (r []models.Comments, err error) { func RecentComments(ctx context.Context, a ...any) (r []models.Comments, err error) {
ctx := a[0].(context.Context) n := helper.ParseArgs(10, a...)
n := a[1].(int)
return model.Finds[models.Comments](ctx, model.Conditions( return model.Finds[models.Comments](ctx, model.Conditions(
model.Where(model.SqlBuilder{ model.Where(model.SqlBuilder{
{"comment_approved", "1"}, {"comment_approved", "1"},
@ -28,10 +32,8 @@ func RecentComments(a ...any) (r []models.Comments, err error) {
// PostComments // PostComments
// param1 context.Context // param1 context.Context
// param2 postId // param2 postId
func PostComments(args ...any) ([]uint64, error) { func PostComments(ctx context.Context, postId uint64, _ ...any) ([]uint64, error) {
ctx := args[0].(context.Context) r, err := model.ChunkFind[models.Comments](ctx, 300, model.Conditions(
postId := args[1].(uint64)
r, err := model.Finds[models.Comments](ctx, model.Conditions(
model.Where(model.SqlBuilder{ model.Where(model.SqlBuilder{
{"comment_approved", "1"}, {"comment_approved", "1"},
{"comment_post_ID", "=", number.IntToString(postId), "int"}, {"comment_post_ID", "=", number.IntToString(postId), "int"},
@ -50,17 +52,132 @@ func PostComments(args ...any) ([]uint64, error) {
}), err }), err
} }
func GetCommentByIds(args ...any) (map[uint64]models.Comments, error) { func GetCommentByIds(ctx context.Context, ids []uint64, _ ...any) (map[uint64]models.Comments, error) {
ctx := args[0].(context.Context) if len(ids) < 1 {
ids := args[1].([]uint64) return nil, nil
m := make(map[uint64]models.Comments)
r, err := model.SimpleFind[models.Comments](ctx, model.SqlBuilder{
{"comment_ID", "in", ""}, {"comment_approved", "1"},
}, "*", slice.ToAnySlice(ids))
if err != nil {
return m, err
} }
return slice.SimpleToMap(r, func(t models.Comments) uint64 { m := make(map[uint64]models.Comments)
return t.CommentId off := 0
}), err for {
id := slice.Slice(ids, off, 500)
if len(id) < 1 {
break
}
r, err := model.Finds[models.Comments](ctx, model.Conditions(
model.Where(model.SqlBuilder{
{"comment_ID", "in", ""}, {"comment_approved", "1"},
}),
model.Fields("*"),
model.In(slice.ToAnySlice(id)),
))
if err != nil {
return m, err
}
for _, comments := range r {
m[comments.CommentId] = comments
}
off += 500
}
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 PostTopCommentNum(ctx context.Context, postId uint64, _ ...any) (int, error) {
v, err := model.GetField[models.Comments](ctx, "count(*) num", model.Conditions(
model.Where(postTopCommentNumWhere(postId)),
))
if err != nil {
return 0, err
}
return str.ToInteger(v, 0), nil
}
func postTopCommentNumWhere(postId uint64) model.SqlBuilder {
threadComments := wpconfig.GetOption("thread_comments")
pageComments := wpconfig.GetOption("page_comments")
where := model.SqlBuilder{
{"comment_approved", "1"},
{"comment_post_ID", "=", number.IntToString(postId), "int"},
}
if pageComments != "1" || threadComments == "1" || "1" == wpconfig.GetOption("thread_comments_depth") {
where = append(where, []string{"comment_parent", "0"})
}
return where
}
func PostCommentsIds(ctx context.Context, postId uint64, page, limit, totalRaw int, order string) ([]uint64, int, error) {
condition := model.Conditions(
model.Where(postTopCommentNumWhere(postId)),
model.TotalRaw(totalRaw),
model.Fields("comment_ID"),
model.Order(model.SqlBuilder{
{"comment_date_gmt", order},
{"comment_ID", "asc"},
}),
)
var r []models.Comments
var total int
var err error
if limit < 1 {
r, err = model.ChunkFind[models.Comments](ctx, 300, condition)
total = len(r)
} else {
r, total, err = model.Pagination[models.Comments](ctx, condition, 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 CommentChildren(ctx context.Context, commentIds []uint64, _ ...any) (r map[uint64][]uint64, err error) {
rr, err := model.Finds[models.Comments](ctx, model.Conditions(
model.Where(model.SqlBuilder{
{"comment_parent", "in", ""},
{"comment_approved", "1"},
}),
model.In(slice.ToAnySlice(commentIds)),
model.Fields("comment_ID,comment_parent"),
))
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
err = nil
}
return
}
rrr := slice.GroupBy(rr, func(v models.Comments) (uint64, uint64) {
return v.CommentParent, v.CommentId
})
r = make(map[uint64][]uint64)
for _, id := range commentIds {
r[id] = rrr[id]
}
return
}
func PreviousCommentNum(ctx context.Context, commentId, postId uint64) (int, error) {
v, err := model.GetField[models.Comments](ctx, "count(*)", model.Conditions(
model.Where(model.SqlBuilder{
{"comment_approved", "1"},
{"comment_post_ID", "=", number.IntToString(postId), "int"},
{"comment_ID", "<", number.IntToString(commentId), "int"},
{"comment_parent", "=", "0", "int"},
}),
))
if err != nil {
return 0, err
}
return str.ToInteger(v, 0), nil
} }

View File

@ -21,17 +21,13 @@ type PostContext struct {
Next models.Posts Next models.Posts
} }
func CategoriesAndTags(a ...any) (terms []models.TermsMy, err error) { func CategoriesAndTags(ctx context.Context, t string, _ ...any) (terms []models.TermsMy, err error) {
ctx := a[0].(context.Context)
t, ok := a[1].(string)
var in = []any{"category", "post_tag"} var in = []any{"category", "post_tag"}
if ok { switch t {
switch t { case constraints.Category:
case constraints.Category: in = []any{"category"}
in = []any{"category"} case constraints.Tag:
case constraints.Tag: in = []any{"post_tag"}
in = []any{"post_tag"}
}
} }
w := model.SqlBuilder{ w := model.SqlBuilder{
{"tt.taxonomy", "in", ""}, {"tt.taxonomy", "in", ""},

View File

@ -11,10 +11,8 @@ import (
"strconv" "strconv"
) )
func GetPostMetaByPostIds(args ...any) (r map[uint64]map[string]any, err error) { func GetPostMetaByPostIds(ctx context.Context, ids []uint64, _ ...any) (r map[uint64]map[string]any, err error) {
r = make(map[uint64]map[string]any) r = make(map[uint64]map[string]any)
ctx := args[0].(context.Context)
ids := args[1].([]uint64)
rr, err := model.Finds[models.PostMeta](ctx, model.Conditions( rr, err := model.Finds[models.PostMeta](ctx, model.Conditions(
model.Where(model.SqlBuilder{{"post_id", "in", ""}}), model.Where(model.SqlBuilder{{"post_id", "in", ""}}),
model.In(slice.ToAnySlice(ids)), model.In(slice.ToAnySlice(ids)),

View File

@ -3,6 +3,7 @@ package dao
import ( import (
"context" "context"
"database/sql" "database/sql"
"errors"
"fmt" "fmt"
"github.com/fthvgb1/wp-go/app/pkg/models" "github.com/fthvgb1/wp-go/app/pkg/models"
"github.com/fthvgb1/wp-go/app/pkg/models/relation" "github.com/fthvgb1/wp-go/app/pkg/models/relation"
@ -15,10 +16,8 @@ import (
"time" "time"
) )
func GetPostsByIds(a ...any) (m map[uint64]models.Posts, err error) { func GetPostsByIds(ctx context.Context, ids []uint64, _ ...any) (m map[uint64]models.Posts, err error) {
ctx := a[0].(context.Context)
m = make(map[uint64]models.Posts) m = make(map[uint64]models.Posts)
ids := a[1].([]uint64)
q := model.Conditions( q := model.Conditions(
model.Where(model.SqlBuilder{{"Id", "in", ""}}), model.Where(model.SqlBuilder{{"Id", "in", ""}}),
model.Join(model.SqlBuilder{ model.Join(model.SqlBuilder{
@ -99,11 +98,10 @@ func GetPostsByIds(a ...any) (m map[uint64]models.Posts, err error) {
return return
} }
func SearchPostIds(args ...any) (ids PostIds, err error) { func SearchPostIds(ctx context.Context, _ string, args ...any) (ids PostIds, err error) {
ctx := args[0].(context.Context) q := args[0].(*model.QueryCondition)
q := args[1].(*model.QueryCondition) page := args[1].(int)
page := args[2].(int) pageSize := args[2].(int)
pageSize := args[3].(int)
q.Fields = "ID" q.Fields = "ID"
res, total, err := model.Pagination[models.Posts](ctx, q, page, pageSize) res, total, err := model.Pagination[models.Posts](ctx, q, page, pageSize)
for _, posts := range res { for _, posts := range res {
@ -118,11 +116,19 @@ func SearchPostIds(args ...any) (ids PostIds, err error) {
return return
} }
func GetMaxPostId(a ...any) (uint64, error) { func GetMaxPostId(ctx context.Context, _ ...any) (uint64, error) {
ctx := a[0].(context.Context) r, err := model.Finds[models.Posts](ctx,
r, err := model.SimpleFind[models.Posts](ctx, model.Conditions(
model.SqlBuilder{{"post_type", "post"}, {"post_status", "publish"}}, model.Where(model.SqlBuilder{
"max(ID) ID", {"post_type", "post"},
{"post_status", "publish"}},
),
model.Fields("ID"),
model.Order(model.SqlBuilder{
{"ID", "desc"},
}),
model.Limit(1),
),
) )
var id uint64 var id uint64
if len(r) > 0 { if len(r) > 0 {
@ -131,9 +137,8 @@ func GetMaxPostId(a ...any) (uint64, error) {
return id, err return id, err
} }
func RecentPosts(a ...any) (r []models.Posts, err error) { func RecentPosts(ctx context.Context, a ...any) (r []models.Posts, err error) {
ctx := a[0].(context.Context) num := helper.ParseArgs(10, a...)
num := a[1].(int)
r, err = model.Finds[models.Posts](ctx, model.Conditions( r, err = model.Finds[models.Posts](ctx, model.Conditions(
model.Where(model.SqlBuilder{ model.Where(model.SqlBuilder{
{"post_type", "post"}, {"post_type", "post"},
@ -146,15 +151,14 @@ func RecentPosts(a ...any) (r []models.Posts, err error) {
return return
} }
func GetPostContext(arg ...any) (r PostContext, err error) { func GetPostContext(ctx context.Context, _ uint64, arg ...any) (r PostContext, err error) {
ctx := arg[0].(context.Context) t := arg[0].(time.Time)
t := arg[1].(time.Time)
next, err := model.FirstOne[models.Posts](ctx, model.SqlBuilder{ next, err := model.FirstOne[models.Posts](ctx, model.SqlBuilder{
{"post_date", ">", t.Format("2006-01-02 15:04:05")}, {"post_date", ">", t.Format("2006-01-02 15:04:05")},
{"post_status", "in", ""}, {"post_status", "in", ""},
{"post_type", "post"}, {"post_type", "post"},
}, "ID,post_title,post_password", nil, []any{"publish"}) }, "ID,post_title,post_password", nil, []any{"publish"})
if err == sql.ErrNoRows { if errors.Is(err, sql.ErrNoRows) {
err = nil err = nil
} }
if err != nil { if err != nil {
@ -165,7 +169,7 @@ func GetPostContext(arg ...any) (r PostContext, err error) {
{"post_status", "in", ""}, {"post_status", "in", ""},
{"post_type", "post"}, {"post_type", "post"},
}, "ID,post_title", model.SqlBuilder{{"post_date", "desc"}}, []any{"publish"}) }, "ID,post_title", model.SqlBuilder{{"post_date", "desc"}}, []any{"publish"})
if err == sql.ErrNoRows { if errors.Is(err, sql.ErrNoRows) {
err = nil err = nil
} }
if err != nil { if err != nil {
@ -178,9 +182,8 @@ func GetPostContext(arg ...any) (r PostContext, err error) {
return return
} }
func MonthPost(args ...any) (r []uint64, err error) { func MonthPost(ctx context.Context, _ string, args ...any) (r []uint64, err error) {
ctx := args[0].(context.Context) year, month := args[0].(string), args[1].(string)
year, month := args[1].(string), args[2].(string)
where := model.SqlBuilder{ where := model.SqlBuilder{
{"post_type", "post"}, {"post_type", "post"},
{"post_status", "publish"}, {"post_status", "publish"},

View File

@ -7,31 +7,26 @@ import (
"github.com/fthvgb1/wp-go/model" "github.com/fthvgb1/wp-go/model"
) )
func GetUserById(a ...any) (r models.Users, err error) { func GetUserById(ctx context.Context, uid uint64, _ ...any) (r models.Users, err error) {
ctx := a[0].(context.Context)
uid := a[1].(uint64)
r, err = model.FindOneById[models.Users](ctx, uid) r, err = model.FindOneById[models.Users](ctx, uid)
return return
} }
func AllUsername(a ...any) (map[string]struct{}, error) { func AllUsername(ctx context.Context, _ ...any) (map[string]uint64, error) {
ctx := a[0].(context.Context)
r, err := model.SimpleFind[models.Users](ctx, model.SqlBuilder{ r, err := model.SimpleFind[models.Users](ctx, model.SqlBuilder{
{"user_status", "=", "0", "int"}, {"user_status", "=", "0", "int"},
}, "user_login") }, "display_name,ID")
if err != nil { if err != nil {
return nil, err return nil, err
} }
return slice.ToMap(r, func(t models.Users) (string, struct{}) { return slice.ToMap(r, func(t models.Users) (string, uint64) {
return t.UserLogin, struct{}{} return t.DisplayName, t.Id
}, true), nil }, true), nil
} }
func GetUserByName(a ...any) (r models.Users, err error) { func GetUserByName(ctx context.Context, u string, _ ...any) (r models.Users, err error) {
u := a[1].(string)
ctx := a[0].(context.Context)
r, err = model.FirstOne[models.Users](ctx, model.SqlBuilder{{ r, err = model.FirstOne[models.Users](ctx, model.SqlBuilder{{
"user_login", u, "display_name", u,
}}, "*", nil) }}, "*", nil)
return return
} }

View File

@ -3,14 +3,21 @@ package db
import ( import (
"context" "context"
"github.com/fthvgb1/wp-go/app/pkg/config" "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/model"
"github.com/fthvgb1/wp-go/safety" "github.com/fthvgb1/wp-go/safety"
_ "github.com/go-sql-driver/mysql" _ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
"log" "log"
"runtime"
) )
var safeDb = safety.NewVar[*sqlx.DB](nil) var safeDb = safety.NewVar[*sqlx.DB](nil)
var showQuerySql func() bool
func GetSqlxDB() *sqlx.DB {
return safeDb.Load()
}
func InitDb() (*safety.Var[*sqlx.DB], error) { func InitDb() (*safety.Var[*sqlx.DB], error) {
c := config.GetConfig() c := config.GetConfig()
@ -36,6 +43,11 @@ func InitDb() (*safety.Var[*sqlx.DB], error) {
if preDb != nil { if preDb != nil {
_ = preDb.Close() _ = preDb.Close()
} }
if showQuerySql == nil {
showQuerySql = reload.BuildFnVal("showQuerySql", false, func() bool {
return config.GetConfig().ShowQuerySql
})
}
return safeDb, err return safeDb, err
} }
@ -44,14 +56,20 @@ func QueryDb(db *safety.Var[*sqlx.DB]) *model.SqlxQuery {
nil, nil,
nil)) nil))
model.SetSelect(query, func(ctx context.Context, a any, s string, args ...any) error { model.SetSelect(query, func(ctx context.Context, a any, s string, args ...any) error {
if config.GetConfig().ShowQuerySql { if showQuerySql() {
go log.Println(model.FormatSql(s, args...)) _, 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...) return query.Selects(ctx, a, s, args...)
}) })
model.SetGet(query, func(ctx context.Context, a any, s string, args ...any) error { model.SetGet(query, func(ctx context.Context, a any, s string, args ...any) error {
if config.GetConfig().ShowQuerySql { if showQuerySql() {
go log.Println(model.FormatSql(s, args...)) _, 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...) return query.Gets(ctx, a, s, args...)
}) })

View File

@ -15,20 +15,24 @@ var logs = safety.NewVar[*log.Logger](nil)
var logFile = safety.NewVar[*os.File](nil) var logFile = safety.NewVar[*os.File](nil)
func InitLogger() error { func InitLogger() error {
c := config.GetConfig()
return SetLogger(c.LogOutput)
}
func SetLogger(loggerFile string) error {
if loggerFile == "" {
loggerFile = "stderr"
}
preFD := logFile.Load() preFD := logFile.Load()
l := &log.Logger{} l := &log.Logger{}
c := config.GetConfig()
if c.LogOutput == "" {
c.LogOutput = "stderr"
}
var out io.Writer var out io.Writer
switch c.LogOutput { switch loggerFile {
case "stdout": case "stdout":
out = os.Stdout out = os.Stdout
case "stderr": case "stderr":
out = os.Stderr out = os.Stderr
default: default:
file, err := os.OpenFile(c.LogOutput, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0777) file, err := os.OpenFile(loggerFile, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0777)
if err != nil { if err != nil {
return err return err
} }

View File

@ -19,7 +19,8 @@ type Comments struct {
CommentParent uint64 `gorm:"column:comment_parent" db:"comment_parent" json:"comment_parent" form:"comment_parent"` CommentParent uint64 `gorm:"column:comment_parent" db:"comment_parent" json:"comment_parent" form:"comment_parent"`
UserId uint64 `gorm:"column:user_id" db:"user_id" json:"user_id" form:"user_id"` UserId uint64 `gorm:"column:user_id" db:"user_id" json:"user_id" form:"user_id"`
//扩展字段 //扩展字段
PostTitle string `db:"post_title"` PostTitle string `db:"post_title"`
UpdateTime time.Time `gorm:"update_time" form:"update_time" json:"update_time" db:"update_time"`
} }
func (w Comments) PrimaryKey() string { func (w Comments) PrimaryKey() string {
@ -29,3 +30,8 @@ func (w Comments) PrimaryKey() string {
func (w Comments) Table() string { func (w Comments) Table() string {
return "wp_comments" return "wp_comments"
} }
type PostComments struct {
Comments
Children []uint64
}

View File

@ -1,23 +1,25 @@
package plugins package plugins
import ( import (
"context"
"github.com/fthvgb1/wp-go/app/pkg/models" "github.com/fthvgb1/wp-go/app/pkg/models"
"github.com/fthvgb1/wp-go/app/wpconfig" "github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/helper/number"
"github.com/fthvgb1/wp-go/helper/slice" "github.com/fthvgb1/wp-go/helper/slice"
str "github.com/fthvgb1/wp-go/helper/strings" str "github.com/fthvgb1/wp-go/helper/strings"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"net/url"
"strconv" "strconv"
"strings" "strings"
) )
type CommentHandler struct { type CommentHandler struct {
*gin.Context *gin.Context
comments []*Comments comments []*Comments
maxDepth int maxDepth int
depth int depth int
isTls bool isTls bool
i CommentHtml i CommentHtml
isThreadComments bool
} }
type Comments struct { type Comments struct {
@ -26,19 +28,18 @@ type Comments struct {
} }
type CommentHtml interface { type CommentHtml interface {
Sort(i, j *Comments) bool FormatLi(c context.Context, m models.Comments, depth, maxDepth, page int, isTls, isThreadComments bool, eo, parent string) string
FormatLi(c *gin.Context, m models.Comments, depth int, isTls bool, eo, parent string) string FloorOrder(i, j models.Comments) bool
} }
func FormatComments(c *gin.Context, i CommentHtml, comments []models.Comments, maxDepth int) string { func FormatComments(c *gin.Context, i CommentHtml, comments []models.Comments, maxDepth int) string {
tree := treeComments(comments) tree := treeComments(comments)
u := c.Request.Header.Get("Referer")
var isTls bool var isTls bool
if u != "" { if c.Request.TLS != nil {
uu, _ := url.Parse(u) isTls = true
if uu.Scheme == "https" { } else {
isTls = true isTls = "https" == strings.ToLower(c.Request.Header.Get("X-Forwarded-Proto"))
}
} }
h := CommentHandler{ h := CommentHandler{
Context: c, Context: c,
@ -48,15 +49,21 @@ func FormatComments(c *gin.Context, i CommentHtml, comments []models.Comments, m
isTls: isTls, isTls: isTls,
i: i, 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() s := str.NewBuilder()
if d.depth > d.maxDepth { if d.depth >= d.maxDepth {
comments = d.findComments(comments) comments = d.findComments(comments)
} }
slice.Sort(comments, d.i.Sort) order := wpconfig.GetOption("comment_order")
slice.Sort(comments, func(i, j *Comments) bool {
if order == "asc" {
return i.CommentDate.Sub(j.CommentDate) < 0
}
return i.CommentDate.Sub(j.CommentDate) > 0
})
for i, comment := range comments { for i, comment := range comments {
eo := "even" eo := "even"
if (i+1)%2 == 0 { if (i+1)%2 == 0 {
@ -68,13 +75,11 @@ func (d CommentHandler) formatComment(comments []*Comments, isTop bool) (html st
parent = "parent" parent = "parent"
fl = true 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 { if fl {
d.depth++ d.depth++
s.WriteString(`<ol class="children">`, d.formatComment(comment.Children, false), `</ol>`) s.WriteString(`<ol class="children">`, d.formatComment(comment.Children), `</ol>`)
if isTop { d.depth--
d.depth = 1
}
} }
s.WriteString("</li><!-- #comment-## -->") s.WriteString("</li><!-- #comment-## -->")
} }
@ -87,7 +92,7 @@ func (d CommentHandler) findComments(comments []*Comments) []*Comments {
var r []*Comments var r []*Comments
for _, comment := range comments { for _, comment := range comments {
tmp := *comment tmp := *comment
comment.Children = nil tmp.Children = nil
r = append(r, &tmp) r = append(r, &tmp)
if len(comment.Children) > 0 { if len(comment.Children) > 0 {
t := d.findComments(comment.Children) t := d.findComments(comment.Children)
@ -137,16 +142,35 @@ func CommentRender() CommonCommentFormat {
type CommonCommentFormat struct { type CommonCommentFormat struct {
} }
func (c CommonCommentFormat) Sort(i, j *Comments) bool { func (c CommonCommentFormat) FormatLi(_ context.Context, m models.Comments, currentDepth, maxDepth, page int, isTls, isThreadComments bool, eo, parent string) string {
order := wpconfig.GetOption("comment_order") return FormatLi(li, m, respondsFn, currentDepth, maxDepth, page, isTls, isThreadComments, eo, parent)
if order == "asc" {
return i.CommentDate.UnixNano() < j.CommentDate.UnixNano()
}
return i.CommentDate.UnixNano() > j.CommentDate.UnixNano()
} }
func (c CommonCommentFormat) FormatLi(ctx *gin.Context, m models.Comments, depth int, isTls bool, eo, parent string) string { func (c CommonCommentFormat) FloorOrder(i, j models.Comments) bool {
return FormatLi(CommonLi(), ctx, m, depth, isTls, eo, parent) return i.CommentId > j.CommentId
}
type RespondFn func(m models.Comments, depth, maxDepth int, isThreadComments bool) string
var respondsFn = Responds(respondTml)
func RespondsFn() RespondFn {
return respondsFn
}
func Responds(respondTml string) RespondFn {
return func(m models.Comments, depth, maxDepth int, isThreadComments bool) string {
if !isThreadComments || depth >= maxDepth {
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 = ` var li = `
@ -158,14 +182,11 @@ var li = `
src="{{Gravatar}}" src="{{Gravatar}}"
srcset="{{Gravatar}} 2x" srcset="{{Gravatar}} 2x"
class="avatar avatar-56 photo" height="56" width="56" loading="lazy"> class="avatar avatar-56 photo" height="56" width="56" loading="lazy">
<b class="fn"> <b class="fn">{{CommentAuthor}}</b>
<a href="{{CommentAuthorUrl}}" rel="external nofollow ugc"
class="url">{{CommentAuthor}}</a>
</b>
<span class="says">说道</span></div><!-- .comment-author --> <span class="says">说道</span></div><!-- .comment-author -->
<div class="comment-metadata"> <div class="comment-metadata">
<a href="/p/{{PostId}}#comment-{{CommentId}}"> <a href="/p/{{PostId}}/comment-page-{{page}}#comment-{{CommentId}}">
<time datetime="{{CommentDateGmt}}">{{CommentDate}}</time> <time datetime="{{CommentDateGmt}}">{{CommentDate}}</time>
</a></div><!-- .comment-metadata --> </a></div><!-- .comment-metadata -->
@ -175,30 +196,34 @@ var li = `
<p>{{CommentContent}}</p> <p>{{CommentContent}}</p>
</div><!-- .comment-content --> </div><!-- .comment-content -->
<div class="reply"> {{respond}}
</article><!-- .comment-body -->
`
var respondTml = `<div class="reply">
<a rel="nofollow" class="comment-reply-link" <a rel="nofollow" class="comment-reply-link"
href="/p/{{PostId}}?replytocom={{CommentId}}#respond" data-commentid="{{CommentId}}" data-postid="{{PostId}}" href="/p/{{PostId}}?replytocom={{CommentId}}#respond" data-commentid="{{CommentId}}" data-postid="{{PostId}}"
data-belowelement="div-comment-{{CommentId}}" data-respondelement="respond" data-belowelement="div-comment-{{CommentId}}" data-respondelement="respond"
data-replyto="回复给{{CommentAuthor}}" data-replyto="回复给{{CommentAuthor}}"
aria-label="回复给{{CommentAuthor}}">回复</a> aria-label="回复给{{CommentAuthor}}">回复</a>
</div> </div>`
</article><!-- .comment-body -->
` func FormatLi(li string, comments models.Comments, respond RespondFn, currentDepth, maxDepth, page int, isTls, isThreadComments bool, eo, parent string) string {
func FormatLi(li string, c *gin.Context, comments models.Comments, depth int, isTls bool, eo, parent string) string {
for k, v := range map[string]string{ for k, v := range map[string]string{
"{{CommentId}}": strconv.FormatUint(comments.CommentId, 10), "{{CommentId}}": strconv.FormatUint(comments.CommentId, 10),
"{{Depth}}": strconv.Itoa(depth), "{{Depth}}": strconv.Itoa(currentDepth),
"{{Gravatar}}": Gravatar(comments.CommentAuthorEmail, isTls), "{{Gravatar}}": Gravatar(comments.CommentAuthorEmail, isTls),
"{{CommentAuthorUrl}}": comments.CommentAuthorUrl, "{{CommentAuthorUrl}}": comments.CommentAuthorUrl,
"{{CommentAuthor}}": comments.CommentAuthor, "{{CommentAuthor}}": comments.CommentAuthor,
"{{PostId}}": strconv.FormatUint(comments.CommentPostId, 10), "{{PostId}}": strconv.FormatUint(comments.CommentPostId, 10),
"{{page}}": strconv.Itoa(page),
"{{CommentDateGmt}}": comments.CommentDateGmt.String(), "{{CommentDateGmt}}": comments.CommentDateGmt.String(),
"{{CommentDate}}": comments.CommentDate.Format("2006-01-02 15:04"), "{{CommentDate}}": comments.CommentDate.Format("2006-01-02 15:04"),
"{{CommentContent}}": comments.CommentContent, "{{CommentContent}}": comments.CommentContent,
"{{eo}}": eo, "{{eo}}": eo,
"{{parent}}": parent, "{{parent}}": parent,
"{{respond}}": respond(comments, currentDepth, maxDepth, isThreadComments),
} { } {
li = strings.Replace(li, k, v, -1) li = strings.Replace(li, k, v, -1)
} }

View File

@ -0,0 +1,141 @@
package main
import (
"context"
"errors"
"fmt"
"github.com/fthvgb1/wp-go/app/pkg/config"
"github.com/fthvgb1/wp-go/app/pkg/dao"
"github.com/fthvgb1/wp-go/app/pkg/logs"
"github.com/fthvgb1/wp-go/app/theme/wp"
"github.com/fthvgb1/wp-go/cache/cachemanager"
"github.com/fthvgb1/wp-go/cache/reload"
"github.com/fthvgb1/wp-go/helper/number"
"github.com/fthvgb1/wp-go/helper/slice"
"github.com/fthvgb1/wp-go/helper/strings"
"github.com/redis/go-redis/v9"
"strconv"
str "strings"
"time"
)
type RdmCache[K comparable, V any] struct {
expired func() time.Duration
rdb *redis.Client
keyFn func(K) string
name string
resFn func(map[string]string) V
saveData func(V) map[string]string
}
func (r *RdmCache[K, V]) SetExpiredTime(f func() time.Duration) {
r.expired = f
}
func (r *RdmCache[K, V]) Get(ctx context.Context, key K) (V, bool) {
var re V
result, err := r.rdb.Exists(ctx, r.keyFn(key)).Result()
if result <= 0 || err != nil {
return re, false
}
rr, err := r.rdb.HGetAll(ctx, r.keyFn(key)).Result()
if errors.Is(err, redis.Nil) {
return re, false
}
if err != nil {
return re, false
}
return r.resFn(rr), true
}
func (r *RdmCache[K, V]) Set(ctx context.Context, key K, val V) {
k := r.keyFn(key)
result, err := r.rdb.HSet(ctx, k, r.saveData(val)).Result()
b, err := r.rdb.Expire(ctx, k, r.expired()).Result()
if err != nil {
fmt.Println(result, b, err)
return
}
fmt.Println(result, err)
}
func (r *RdmCache[K, V]) GetExpireTime(ctx context.Context) time.Duration {
return r.expired()
}
func (r *RdmCache[K, V]) Ttl(ctx context.Context, key K) time.Duration {
result, err := r.rdb.TTL(ctx, r.keyFn(key)).Result()
if err != nil {
return 0
}
return result
}
func (r *RdmCache[K, V]) Flush(ctx context.Context) {
fmt.Println("flush redis cache")
}
func (r *RdmCache[K, V]) Del(ctx context.Context, key ...K) {
r.rdb.Del(ctx, slice.Map(key, r.keyFn)...)
}
func (r *RdmCache[K, V]) ClearExpired(ctx context.Context) {
fmt.Println("clear expired redis cache")
}
// RedisCache use step:
// 1 go build -gcflags all="-N -l" --race -buildmode=plugin -o redisCache.so main.go && cp ./redisCache.so ../wp-go/plugins/
// 2 wp-go config add redisCache plugin
func RedisCache(h *wp.Handle) {
vv, ok := cachemanager.GetMapCache[string, dao.PostIds]("listPostIds")
if ok {
_, ok := any(vv.Cache).(*RdmCache[string, dao.PostIds])
if ok {
return
}
}
reload.AppendOnceFn(func() {
err := cachemanager.SetMapCache("listPostIds", vv)
if err != nil {
logs.Error(err, "set recovery listPostIds cache err")
} else {
cachemanager.PushOrSetFlush(cachemanager.Queue{Name: "listPostIds", Fn: vv.Flush})
cachemanager.PushOrSetClearExpired(cachemanager.Queue{Name: "listPostIds", Fn: vv.Flush})
fmt.Println("recovery listPostIds cache ok")
}
})
rdm := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "", // no password set
DB: 0, // use default DB
})
r := RdmCache[string, dao.PostIds]{
expired: func() time.Duration {
return time.Minute
},
keyFn: func(u string) string {
return strings.Join("postIds:", u)
},
rdb: rdm,
name: "",
resFn: func(m map[string]string) dao.PostIds {
return dao.PostIds{
Ids: slice.Map(str.Split(m["ids"], ","), strings.ToInt[uint64]),
Length: strings.ToInt[int](m["length"]),
}
},
saveData: func(ids dao.PostIds) map[string]string {
t := slice.Map(ids.Ids, number.IntToString[uint64])
return map[string]string{
"ids": str.Join(t, ","),
"length": strconv.Itoa(ids.Length),
}
},
}
cachemanager.NewMapCache[string, dao.PostIds](&r, nil, dao.SearchPostIds, config.GetConfig().CacheTime.PostListCacheTime, "listPostIds", func() time.Duration {
return config.GetConfig().CacheTime.PostListCacheTime
})
fmt.Println("redis cache inited ok")
}

View File

@ -0,0 +1,50 @@
module redisCache
go 1.21
require (
github.com/fthvgb1/wp-go v0.0.0-20231210111549-d72bed0c8c4e
github.com/redis/go-redis/v9 v9.3.0
)
replace github.com/fthvgb1/wp-go v0.0.0-20231210111549-d72bed0c8c4e => ../wp-go
require (
github.com/bytedance/sonic v1.10.2 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
github.com/chenzhuoyu/iasm v0.9.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dlclark/regexp2 v1.10.0 // indirect
github.com/elliotchance/phpserialize v1.3.3 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sessions v0.0.5 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/gin-gonic/gin v1.9.1 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.16.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/gorilla/context v1.1.2 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
github.com/gorilla/sessions v1.2.2 // indirect
github.com/jmoiron/sqlx v1.3.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
golang.org/x/arch v0.6.0 // indirect
golang.org/x/crypto v0.15.0 // indirect
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect
golang.org/x/net v0.18.0 // indirect
golang.org/x/sys v0.14.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View File

@ -3,35 +3,134 @@ package plugins
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/fthvgb1/wp-go/app/cmd/cachemanager"
"github.com/fthvgb1/wp-go/app/pkg/config" "github.com/fthvgb1/wp-go/app/pkg/config"
"github.com/fthvgb1/wp-go/app/pkg/logs"
"github.com/fthvgb1/wp-go/app/pkg/models" "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/cache/reload"
"github.com/fthvgb1/wp-go/helper" "github.com/fthvgb1/wp-go/helper"
"github.com/fthvgb1/wp-go/helper/maps"
str "github.com/fthvgb1/wp-go/helper/strings"
"github.com/fthvgb1/wp-go/plugin/digest" "github.com/fthvgb1/wp-go/plugin/digest"
"github.com/fthvgb1/wp-go/safety"
"regexp" "regexp"
"strings" "strings"
"time" "time"
"unicode/utf8"
) )
var digestCache *cache.MapCache[uint64, string]
var more = regexp.MustCompile("<!--more(.*?)?-->") var more = regexp.MustCompile("<!--more(.*?)?-->")
var removeWpBlock = regexp.MustCompile("<!-- /?wp:.*-->") var removeWpBlock = regexp.MustCompile("<!-- /?wp:.*-->")
type DigestConfig struct {
DigestWordCount int `yaml:"digestWordCount"`
DigestAllowTag string `yaml:"digestAllowTag"`
DigestRegex string `yaml:"digestRegex"`
DigestTagOccupyNum []struct {
Tag string `yaml:"tag"`
Num int `yaml:"num"`
ChuckOvered bool `yaml:"chuckOvered"`
EscapeCharacter []struct {
Tags string `yaml:"tags"`
Character []string `yaml:"character"`
Num int `yaml:"num"`
ChuckOvered bool `yaml:"chuckOvered"`
} `yaml:"escapeCharacter"`
} `yaml:"digestTagOccupyNum"`
specialSolve map[string]digest.SpecialSolveConf
}
var digestConfig *safety.Var[DigestConfig]
func InitDigestCache() { func InitDigestCache() {
digestCache = cachemanager.MapCacheBy[uint64](digestRaw, config.GetConfig().CacheTime.DigestCacheTime) cachemanager.NewMemoryMapCache(nil, digestRaw, config.GetConfig().CacheTime.DigestCacheTime, "digestPlugin", func() time.Duration {
return config.GetConfig().CacheTime.DigestCacheTime
})
digestConfig = reload.VarsBy(func() DigestConfig {
c, err := config.GetCustomizedConfig[DigestConfig]()
if err != nil {
logs.Error(err, "get digest config fail")
c.DigestWordCount = config.GetConfig().DigestWordCount
c.DigestAllowTag = config.GetConfig().DigestAllowTag
return c
}
if c.DigestRegex != "" {
digest.SetQutos(c.DigestRegex)
}
if len(c.DigestTagOccupyNum) <= 1 {
return c
}
c.specialSolve = ParseDigestConf(c)
return c
}, "digestConfig")
}
func ParseDigestConf(c DigestConfig) map[string]digest.SpecialSolveConf {
specialSolve := map[string]digest.SpecialSolveConf{}
for _, item := range c.DigestTagOccupyNum {
tags := strings.Split(strings.ReplaceAll(item.Tag, " ", ""), "<")
for _, tag := range tags {
if tag == "" {
continue
}
ec := make(map[rune]digest.SpecialSolve)
specialTags := make(map[string]digest.SpecialSolve)
tag = str.Join("<", tag)
if len(item.EscapeCharacter) > 0 {
for _, esc := range item.EscapeCharacter {
for _, i := range esc.Character {
s := []rune(i)
if len(s) == 1 {
ec[s[0]] = digest.SpecialSolve{
Num: esc.Num,
ChuckOvered: esc.ChuckOvered,
}
}
}
if esc.Tags == "" {
continue
}
tagss := strings.Split(strings.ReplaceAll(esc.Tags, " ", ""), "<")
for _, t := range tagss {
if t == "" {
continue
}
t = str.Join("<", t)
specialTags[t] = digest.SpecialSolve{
Num: esc.Num,
ChuckOvered: esc.ChuckOvered,
}
}
}
}
v, ok := specialSolve[tag]
if !ok {
specialSolve[tag] = digest.SpecialSolveConf{
Num: item.Num,
ChuckOvered: item.ChuckOvered,
EscapeCharacter: ec,
Tags: specialTags,
}
continue
}
v.Num = item.Num
v.ChuckOvered = item.ChuckOvered
v.EscapeCharacter = maps.Merge(v.EscapeCharacter, ec)
v.Tags = maps.Merge(v.Tags, specialTags)
specialSolve[tag] = v
}
}
return specialSolve
} }
func RemoveWpBlock(s string) string { func RemoveWpBlock(s string) string {
return removeWpBlock.ReplaceAllString(s, "") return removeWpBlock.ReplaceAllString(s, "")
} }
func digestRaw(arg ...any) (string, error) { func digestRaw(ctx context.Context, id uint64, arg ...any) (string, error) {
ctx := arg[0].(context.Context)
s := arg[1].(string) s := arg[1].(string)
id := arg[2].(uint64)
limit := arg[3].(int) limit := arg[3].(int)
if limit < 0 { if limit < 0 {
return s, nil return s, nil
@ -47,12 +146,22 @@ func digestRaw(arg ...any) (string, error) {
func Digests(content string, id uint64, limit int, fn func(id uint64, content, closeTag string) string) string { func Digests(content string, id uint64, limit int, fn func(id uint64, content, closeTag string) string) string {
closeTag := "" closeTag := ""
content = RemoveWpBlock(content) content = RemoveWpBlock(content)
tag := config.GetConfig().DigestAllowTag c := digestConfig.Load()
tag := c.DigestAllowTag
if tag == "" { if tag == "" {
tag = "<a><b><blockquote><br><cite><code><dd><del><div><dl><dt><em><h1><h2><h3><h4><h5><h6><i><img><li><ol><p><pre><span><strong><ul>" tag = "<a><b><blockquote><br><cite><code><dd><del><div><dl><dt><em><h1><h2><h3><h4><h5><h6><i><img><li><ol><p><pre><span><strong><ul>"
} }
content = digest.StripTags(content, tag) content = digest.StripTags(content, tag)
content, closeTag = digest.Html(content, limit) length := utf8.RuneCountInString(content) + 1
if length <= limit {
return content
}
if len(c.specialSolve) > 0 {
content, closeTag = digest.CustomizeHtml(content, limit, c.specialSolve)
} else {
content, closeTag = digest.Html(content, limit)
}
if fn == nil { if fn == nil {
return PostsMore(id, content, closeTag) return PostsMore(id, content, closeTag)
} }
@ -69,7 +178,7 @@ func PostsMore(id uint64, content, closeTag string) string {
} }
func Digest(ctx context.Context, post *models.Posts, limit int) { func Digest(ctx context.Context, post *models.Posts, limit int) {
content, _ := digestCache.GetCache(ctx, post.Id, time.Second, ctx, post.PostContent, post.Id, limit) content, _ := cachemanager.GetBy[string]("digestPlugin", ctx, post.Id, time.Second, ctx, post.PostContent, post.Id, limit)
post.PostContent = content post.PostContent = content
} }

View File

@ -2,8 +2,12 @@ package plugins
import ( import (
"fmt" "fmt"
"github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/helper"
str "github.com/fthvgb1/wp-go/helper/strings" str "github.com/fthvgb1/wp-go/helper/strings"
"net/url"
"regexp" "regexp"
"strconv"
"strings" "strings"
) )
@ -18,6 +22,13 @@ type PageEle struct {
func TwentyFifteenPagination() PageEle { func TwentyFifteenPagination() PageEle {
return twentyFifteen return twentyFifteen
} }
func TwentyFifteenCommentPagination() CommentPageEle {
return twentyFifteenComment
}
type CommentPageEle struct {
PageEle
}
var twentyFifteen = PageEle{ var twentyFifteen = PageEle{
PrevEle: `<a class="prev page-numbers" href="%s">上一页</a>`, PrevEle: `<a class="prev page-numbers" href="%s">上一页</a>`,
@ -28,6 +39,12 @@ var twentyFifteen = PageEle{
CurrentEle: `<span aria-current="page" class="page-numbers current"> CurrentEle: `<span aria-current="page" class="page-numbers current">
<span class="meta-nav screen-reader-text"> </span>%d</span>`, <span class="meta-nav screen-reader-text"> </span>%d</span>`,
} }
var twentyFifteenComment = CommentPageEle{
PageEle{
PrevEle: `<div class="nav-previous"><a href="%s">%s</a></div>`,
NextEle: `<div class="nav-next"><a href="%s">%s</a></div>`,
},
}
func (p PageEle) Current(page, totalPage, totalRow int) string { func (p PageEle) Current(page, totalPage, totalRow int) string {
return fmt.Sprintf(p.CurrentEle, page) return fmt.Sprintf(p.CurrentEle, page)
@ -50,8 +67,22 @@ func (p PageEle) Middle(page int, url string) string {
} }
var reg = regexp.MustCompile(`(/page)/(\d+)`) var reg = regexp.MustCompile(`(/page)/(\d+)`)
var commentReg = regexp.MustCompile(`/comment-page-(\d+)`)
var queryParam = []string{"paged", "cat", "m", "author", "tag"}
func (p PageEle) Urls(u url.URL, page int, isTLS bool) string {
var path, query = u.Path, u.RawQuery
if path == "/" {
v := u.Query()
for _, q := range queryParam {
if v.Get(q) != "" {
v.Set("paged", strconv.Itoa(page))
return str.Join(path, "?", v.Encode())
}
}
}
func (p PageEle) Url(path, query string, page int) string {
if !strings.Contains(path, "/page/") { if !strings.Contains(path, "/page/") {
path = fmt.Sprintf("%s%s", path, "/page/1") path = fmt.Sprintf("%s%s", path, "/page/1")
} }
@ -65,5 +96,81 @@ func (p PageEle) Url(path, query string, page int) string {
if path == "" { if path == "" {
path = "/" path = "/"
} }
return str.Join(path, query) if query != "" {
return str.Join(path, "?", query)
}
return path
}
func (p CommentPageEle) Urls(u url.URL, page int, isTLS bool) string {
var path, query = u.Path, u.RawQuery
if path == "/" {
v := u.Query()
if v.Get("p") != "" {
v.Set("cpage", strconv.Itoa(page))
return str.Join(path, "?", v.Encode(), "#comments")
}
}
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)
ur := path
if query != "" {
ur = str.Join(path, "?", query)
}
ur = str.Join(ur, "#comments")
return ur
}
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", "较新评论", "较早评论"))
}
type PaginationNav struct {
Currents func(page, totalPage, totalRows int) string
Prevs func(url string) string
Nexts func(url string) string
Dotss func() string
Middles func(page int, url string) string
Urlss func(u url.URL, page int, isTLS bool) string
}
func (p PaginationNav) Current(page, totalPage, totalRows int) string {
return p.Currents(page, totalPage, totalRows)
}
func (p PaginationNav) Prev(url string) string {
return p.Prevs(url)
}
func (p PaginationNav) Next(url string) string {
return p.Nexts(url)
}
func (p PaginationNav) Dots() string {
return p.Dotss()
}
func (p PaginationNav) Middle(page int, url string) string {
return p.Middles(page, url)
}
func (p PaginationNav) Urls(u url.URL, page int, isTLS bool) string {
return p.Urlss(u, page, isTLS)
} }

View File

@ -2,12 +2,29 @@ package apply
import "github.com/fthvgb1/wp-go/safety" import "github.com/fthvgb1/wp-go/safety"
var fn safety.Var[any] var contains = safety.NewMap[string, any]()
func SetFn(f any) { func SetVal(key string, val any) {
fn.Store(f) contains.Store(key, val)
} }
func UsePlugins() any { func DelVal(key string) {
return fn.Load() contains.Delete(key)
}
func GetVal[V any](key string) (V, bool) {
v, ok := contains.Load(key)
if !ok {
var vv V
return vv, ok
}
return v.(V), ok
}
func GetRawVal(key string) (any, bool) {
return contains.Load(key)
}
func GetPlugins() any {
v, _ := contains.Load("wp-plugins")
return v
} }

View File

@ -22,7 +22,7 @@ type Options struct {
Linehover bool `json:"linehover,omitempty"` Linehover bool `json:"linehover,omitempty"`
RawcodeDbclick bool `json:"rawcodeDbclick,omitempty"` RawcodeDbclick bool `json:"rawcodeDbclick,omitempty"`
TextOverflow string `json:"textOverflow,omitempty"` TextOverflow string `json:"textOverflow,omitempty"`
Linenumbers int64 `json:"linenumbers,omitempty"` Linenumbers bool `json:"linenumbers,omitempty"`
Theme string `json:"theme,omitempty"` Theme string `json:"theme,omitempty"`
Language string `json:"language,omitempty"` Language string `json:"language,omitempty"`
RetainCssClasses bool `json:"retainCssClasses,omitempty"` RetainCssClasses bool `json:"retainCssClasses,omitempty"`
@ -58,7 +58,7 @@ func EnlighterJS(h *wp.Handle) {
Linehover: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-linehover", true), Linehover: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-linehover", true),
RawcodeDbclick: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-rawcodedbclick", true), RawcodeDbclick: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-rawcodedbclick", true),
TextOverflow: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-textoverflow", "break"), TextOverflow: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-textoverflow", "break"),
Linenumbers: maps.GetStrAnyValWithDefaults[int64](opp, "enlighterjs-linenumbers", 1), Linenumbers: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-linenumbers", true),
Theme: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-theme", "enlighter"), Theme: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-theme", "enlighter"),
Language: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-language", "generic"), Language: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-language", "generic"),
RetainCssClasses: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-retaincss", false), RetainCssClasses: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-retaincss", false),

View File

@ -66,7 +66,7 @@ func LoadPlugins() {
} }
RegisterPlugin(name, plu) RegisterPlugin(name, plu)
} }
apply.SetFn(func(h *wp.Handle) { apply.SetVal("wp-plugins", func(h *wp.Handle) {
UsePlugins(h) UsePlugins(h)
}) })
} }

View File

@ -7,7 +7,7 @@ import (
) )
func HiddenLogin(h *wp.Handle) { func HiddenLogin(h *wp.Handle) {
h.PushComponentFilterFn(widgets.Meta, func(h *wp.Handle, s string, args ...any) string { h.AddActionFilter(widgets.Meta, func(h *wp.Handle, s string, args ...any) string {
return str.Replace(s, map[string]string{ return str.Replace(s, map[string]string{
`<li><a href="/wp-login.php">登录</a></li>`: "", `<li><a href="/wp-login.php">登录</a></li>`: "",
`<li><a href="/feed">登录</a></li>`: "", `<li><a href="/feed">登录</a></li>`: "",

View File

@ -7,7 +7,7 @@ import (
) )
func Tt(h *wp.Handle) { func Tt(h *wp.Handle) {
h.HookHandle(constraints.PipeMiddleware, func(call wp.HandleCall) (wp.HandleCall, bool) { h.HookHandle(constraints.PipeMiddleware, constraints.AllScene, func(call wp.HandleCall) (wp.HandleCall, bool) {
return call, false return call, false
}) })
/*h.PushPipeHook(constraints.Home, func(pipe wp.Pipe) (wp.Pipe, bool) { /*h.PushPipeHook(constraints.Home, func(pipe wp.Pipe) (wp.Pipe, bool) {

147
app/route/route.go Normal file
View File

@ -0,0 +1,147 @@
package route
import (
"github.com/fthvgb1/wp-go/app/actions"
"github.com/fthvgb1/wp-go/app/middleware"
"github.com/fthvgb1/wp-go/app/pkg/config"
"github.com/fthvgb1/wp-go/app/pkg/constraints"
"github.com/fthvgb1/wp-go/app/theme"
"github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/cache/reload"
"github.com/fthvgb1/wp-go/helper/slice"
"github.com/fthvgb1/wp-go/helper/slice/mockmap"
str "github.com/fthvgb1/wp-go/helper/strings"
"github.com/gin-contrib/gzip"
"github.com/gin-contrib/pprof"
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin"
)
type GinSetter func(*gin.Engine)
var setters mockmap.Map[string, GinSetter]
var setterHooks []func(item mockmap.Item[string, GinSetter]) (mockmap.Item[string, GinSetter], bool)
// SetGinAction 方便插件在init时使用
func SetGinAction(name string, hook GinSetter, orders ...float64) {
setters.Set(name, hook, orders...)
}
func HookGinSetter(fn func(item mockmap.Item[string, GinSetter]) (mockmap.Item[string, GinSetter], bool)) {
setterHooks = append(setterHooks, fn)
}
// DelGinSetter 方便插件在init时使用
func DelGinSetter(name string) {
setterHooks = append(setterHooks, func(item mockmap.Item[string, GinSetter]) (mockmap.Item[string, GinSetter], bool) {
return item, item.Name != name
})
}
func SetupRouter() *gin.Engine {
// Disable Console Color
// gin.DisableConsoleColor()
r := gin.New()
c := config.GetConfig()
SetGinAction("initTrustIp", func(r *gin.Engine) {
if len(c.TrustIps) > 0 {
err := r.SetTrustedProxies(c.TrustIps)
if err != nil {
panic(err)
}
}
}, 99.5)
SetGinAction("setTemplate", func(r *gin.Engine) {
r.HTMLRender = theme.BuildTemplate()
wpconfig.SetTemplateFs(theme.TemplateFs)
}, 90.5)
siteFlowLimitMiddleware, siteFlow := middleware.FlowLimit(c.MaxRequestSleepNum, c.MaxRequestNum, c.CacheTime.SleepTime)
reload.Append(func() {
c = config.GetConfig()
siteFlow(c.MaxRequestSleepNum, c.MaxRequestNum, c.CacheTime.SleepTime)
}, "site-flowLimit-config")
SetGinAction("setGlobalMiddleware", func(r *gin.Engine) {
r.Use(
gin.Logger(),
middleware.ValidateServerNames(),
middleware.RecoverAndSendMail(gin.DefaultErrorWriter),
siteFlowLimitMiddleware,
middleware.SetStaticFileCache,
)
}, 88.5)
SetGinAction("setGzip", func(r *gin.Engine) {
//gzip 因为一般会用nginx做反代时自动使用gzip,所以go这边本身可以不用
if c.Gzip {
r.Use(gzip.Gzip(gzip.DefaultCompression, gzip.WithExcludedPaths([]string{
"/wp-includes/", "/wp-content/",
})))
}
}, 87.6)
SetGinAction("setWpDir", func(r *gin.Engine) {
if c.WpDir == "" {
panic("wordpress path can't be empty")
}
r.Static("/wp-content/uploads", str.Join(c.WpDir, "/wp-content/uploads"))
r.Static("/wp-content/themes", str.Join(c.WpDir, "/wp-content/themes"))
r.Static("/wp-content/plugins", str.Join(c.WpDir, "/wp-content/plugins"))
r.Static("/wp-includes/css", str.Join(c.WpDir, "/wp-includes/css"))
r.Static("/wp-includes/fonts", str.Join(c.WpDir, "/wp-includes/fonts"))
r.Static("/wp-includes/js", str.Join(c.WpDir, "/wp-includes/js"))
}, 86.1)
SetGinAction("setSession", func(r *gin.Engine) {
store := cookie.NewStore([]byte("secret"))
r.Use(sessions.Sessions("go-wp", store))
}, 85.1)
SetGinAction("setRoute", func(r *gin.Engine) {
r.GET("/", actions.Feed, middleware.SearchLimit(c.SingleIpSearchNum),
actions.ThemeHook(constraints.Home))
r.GET("/page/:page", actions.ThemeHook(constraints.Home))
r.GET("/p/category/:category", actions.ThemeHook(constraints.Category))
r.GET("/p/category/:category/page/:page", actions.ThemeHook(constraints.Category))
r.GET("/p/tag/:tag", actions.ThemeHook(constraints.Tag))
r.GET("/p/tag/:tag/page/:page", actions.ThemeHook(constraints.Tag))
r.GET("/p/date/:year/:month", actions.ThemeHook(constraints.Archive))
r.GET("/p/date/:year/:month/page/:page", actions.ThemeHook(constraints.Archive))
r.GET("/p/author/:author", actions.ThemeHook(constraints.Author))
r.GET("/p/author/:author/page/:page", actions.ThemeHook(constraints.Author))
r.POST("/login", actions.Login)
r.GET("/p/:id", actions.ThemeHook(constraints.Detail))
r.GET("/p/:id/comment-page-:page", actions.ThemeHook(constraints.Detail))
r.GET("/p/:id/feed", actions.PostFeed)
r.GET("/feed", actions.SiteFeed)
r.GET("/comments/feed", actions.CommentsFeed)
commentMiddleWare, _ := middleware.FlowLimit(c.MaxRequestSleepNum, 5, c.CacheTime.SleepTime)
r.POST("/comment", commentMiddleWare, actions.PostComment)
r.NoRoute(actions.ThemeHook(constraints.NoRoute))
}, 84.6)
SetGinAction("setpprof", func(r *gin.Engine) {
if c.Pprof != "" {
pprof.Register(r, c.Pprof)
}
}, 80.8)
for _, hook := range setterHooks {
setters = slice.FilterAndMap(setters, hook)
}
slice.SimpleSort(setters, slice.DESC, func(t mockmap.Item[string, GinSetter]) float64 {
return t.Order
})
for _, fn := range setters {
fn.Value(r)
}
return r
}

View File

@ -3,6 +3,7 @@ package theme
import ( import (
"embed" "embed"
"github.com/fthvgb1/wp-go/multipTemplate" "github.com/fthvgb1/wp-go/multipTemplate"
"github.com/fthvgb1/wp-go/safety"
"html/template" "html/template"
"io/fs" "io/fs"
"path/filepath" "path/filepath"
@ -12,20 +13,36 @@ import (
//go:embed *[^.go] //go:embed *[^.go]
var TemplateFs embed.FS var TemplateFs embed.FS
var templates map[string]*template.Template //方便外部获取模板render后的字符串不然在gin中获取不了 var templates = safety.NewMap[string, *template.Template]() //方便外部获取模板render后的字符串不然在gin中获取不了
func Template() *multipTemplate.MultipleFsTemplate { var multiple *multipTemplate.MultipleFsTemplate
t := multipTemplate.NewFsTemplate(TemplateFs)
templates = t.Template func BuildTemplate() *multipTemplate.MultipleFsTemplate {
t.FuncMap = FuncMap() if multiple != nil {
commonTemplate(t) tt := multipTemplate.NewFsTemplate(TemplateFs)
/*t.AddTemplate("twentyfifteen/*[^layout]/*.gohtml", FuncMap(), "twentyfifteen/layout/*.gohtml"). //单个主题设置 commonTemplate(tt)
AddTemplate("twentyseventeen/*[^layout]/*.gohtml", FuncMap(), "twentyseventeen/layout/*.gohtml")*/ for k, v := range map[string]*template.Template(any(tt.Template).(multipTemplate.TemplateMaps)) {
return t multiple.Template.Store(k, v)
}
} else {
multiple = multipTemplate.NewFsTemplates(TemplateFs, templates)
commonTemplate(multiple)
}
/*t.AddTemplate("twentyfifteen/*[^layout]/*.gohtml", FuncMap(), "twentyfifteen/layout/*.gohtml","wp/template.gohtml"). //单个主题设置
AddTemplate("twentyseventeen/*[^layout]/*.gohtml", FuncMap(), "twentyseventeen/layout/*.gohtml","wp/template.gohtml")*/
return multiple
}
func GetMultipleTemplate() *multipTemplate.MultipleFsTemplate {
if multiple == nil {
BuildTemplate()
}
return multiple
} }
func GetTemplate(name string) (*template.Template, bool) { func GetTemplate(name string) (*template.Template, bool) {
t, ok := templates[name] t, ok := templates.Load(name)
return t, ok return t, ok
} }
@ -35,10 +52,11 @@ func commonTemplate(t *multipTemplate.MultipleFsTemplate) {
if err != nil { if err != nil {
panic(err) panic(err)
} }
funMap := FuncMap()
for _, main := range m { for _, main := range m {
file := filepath.Base(main) file := filepath.Base(main)
dir := strings.Split(main, "/")[0] dir := strings.Split(main, "/")[0]
templ := template.Must(template.New(file).Funcs(t.FuncMap).ParseFS(t.Fs, main, filepath.Join(dir, "layout/*.gohtml"), "wp/template.gohtml")) templ := template.Must(template.New(file).Funcs(funMap).ParseFS(t.Fs, main, filepath.Join(dir, "layout/*.gohtml"), "wp/template.gohtml"))
t.SetTemplate(main, templ) t.SetTemplate(main, templ)
} }
} }
@ -55,6 +73,10 @@ func IsTemplateDirExists(tml string) bool {
} }
func IsTemplateExists(tml string) bool { func IsTemplateExists(tml string) bool {
t, ok := templates[tml] t, ok := templates.Load(tml)
return ok && t != nil return ok && t != nil
} }
func SetTemplate(name string, val *template.Template) {
templates.Store(name, val)
}

View File

@ -1,25 +1,35 @@
package theme package theme
import ( import (
"github.com/fthvgb1/wp-go/app/theme/twentyfifteen"
"github.com/fthvgb1/wp-go/app/theme/wp" "github.com/fthvgb1/wp-go/app/theme/wp"
str "github.com/fthvgb1/wp-go/helper/strings"
"github.com/fthvgb1/wp-go/safety" "github.com/fthvgb1/wp-go/safety"
) )
var themeMap = safety.NewMap[string, func(*wp.Handle)]() var themeMap = safety.NewMap[string, func(*wp.Handle)]()
func AddThemeHookFunc(name string, fn func(handle *wp.Handle)) { func AddTheme(name string, fn func(handle *wp.Handle)) {
if _, ok := themeMap.Load(name); ok {
panic("exists same name theme")
}
themeMap.Store(name, fn) themeMap.Store(name, fn)
} }
func DelTheme(name string) {
themeMap.Delete(name)
}
func GetTheme(name string) (func(*wp.Handle), bool) {
return themeMap.Load(name)
}
func IsThemeHookFuncExist(name string) bool {
_, ok := themeMap.Load(name)
return ok
}
func Hook(themeName string, h *wp.Handle) { func Hook(themeName string, h *wp.Handle) {
fn, ok := themeMap.Load(themeName) fn, ok := themeMap.Load(themeName)
if ok && fn != nil { if ok && fn != nil {
fn(h) fn(h)
return return
} }
twentyfifteen.Hook(h) panic(str.Join("theme ", themeName, " don't exist"))
} }

View File

@ -7,37 +7,31 @@ import (
"time" "time"
) )
var comFn = template.FuncMap{
"unescaped": func(s string) any {
return template.HTML(s)
},
"dateCh": func(t time.Time) any {
return t.Format("2006年 01月 02日")
},
"timeFormat": func(t time.Time, format string) any {
return t.Format(format)
},
"getOption": func(k string) string {
return wpconfig.GetOption(k)
},
"getLang": wpconfig.GetLang,
"postsFn": postsFn,
"exec": func(fn func() string) template.HTML {
return template.HTML(fn())
},
}
func postsFn(fn func(models.Posts) string, a models.Posts) string { func postsFn(fn func(models.Posts) string, a models.Posts) string {
return fn(a) return fn(a)
} }
func FuncMap() template.FuncMap { func FuncMap() template.FuncMap {
return comFn return template.FuncMap{
} "unescaped": func(s string) any {
return template.HTML(s)
func addTemplateFunc(fnName string, fn any) { },
if _, ok := comFn[fnName]; ok { "dateCh": func(t time.Time) any {
panic("exists same name func") return t.Format("2006年 01月 02日")
},
"timeFormat": func(t time.Time, format string) any {
return t.Format(format)
},
"getOption": func(k string) string {
return wpconfig.GetOption(k)
},
"getLang": wpconfig.GetLang,
"postsFn": postsFn,
"exec": func(fn func() string) template.HTML {
return template.HTML(fn())
},
"callFuncString": func(fn func(string) string, s string) template.HTML {
return template.HTML(fn(s))
},
} }
comFn[fnName] = fn
} }

View File

@ -8,17 +8,17 @@ import (
) )
func InitTheme() { func InitTheme() {
AddThemeHookFunc(twentyfifteen.ThemeName, twentyfifteen.Hook) AddTheme(twentyfifteen.ThemeName, twentyfifteen.Hook)
AddThemeHookFunc(twentyseventeen.ThemeName, twentyseventeen.Hook) AddTheme(twentyseventeen.ThemeName, twentyseventeen.Hook)
} }
func GetCurrentTemplateName() string { func GetCurrentTheme() string {
tmlp := config.GetConfig().Theme themeName := config.GetConfig().Theme
if tmlp == "" { if themeName == "" {
tmlp = wpconfig.GetOption("template") themeName = wpconfig.GetOption("template")
} }
if !IsTemplateDirExists(tmlp) { if !IsTemplateDirExists(themeName) {
tmlp = "twentyfifteen" themeName = "twentyfifteen"
} }
return tmlp return themeName
} }

View File

@ -1,9 +1,9 @@
package twentyfifteen package twentyfifteen
import ( import (
"github.com/fthvgb1/wp-go/app/cmd/reload"
"github.com/fthvgb1/wp-go/app/pkg/constraints" "github.com/fthvgb1/wp-go/app/pkg/constraints"
"github.com/fthvgb1/wp-go/app/theme/wp" "github.com/fthvgb1/wp-go/app/theme/wp"
"github.com/fthvgb1/wp-go/cache/reload"
str "github.com/fthvgb1/wp-go/helper/strings" str "github.com/fthvgb1/wp-go/helper/strings"
) )
@ -81,7 +81,7 @@ var imgStyle = `.site-header {
} }
}` }`
var header = reload.Vars(constraints.Defaults) var header = reload.Vars(constraints.Defaults, "twentyfifteen-customheader")
func calCustomHeaderImg(h *wp.Handle) (r string, rand bool) { func calCustomHeaderImg(h *wp.Handle) (r string, rand bool) {
img, rand := h.GetCustomHeaderImg() img, rand := h.GetCustomHeaderImg()

View File

@ -31,11 +31,7 @@
{{end}} {{end}}
</div> </div>
<footer id="colophon" class="site-footer"> {{template "common/colophon" .}}
<div class="site-info">
<a href="https://cn.wordpress.org/" class="imprint">自豪地采用WordPress</a>
</div>
</footer>
</div> </div>
{{template "layout/footer" .}} {{template "layout/footer" .}}

View File

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

View File

@ -6,7 +6,7 @@ import (
"github.com/fthvgb1/wp-go/app/plugins" "github.com/fthvgb1/wp-go/app/plugins"
"github.com/fthvgb1/wp-go/app/theme/wp" "github.com/fthvgb1/wp-go/app/theme/wp"
"github.com/fthvgb1/wp-go/app/theme/wp/components" "github.com/fthvgb1/wp-go/app/theme/wp/components"
"github.com/fthvgb1/wp-go/app/theme/wp/components/widget" "github.com/fthvgb1/wp-go/app/theme/wp/middleware"
"github.com/fthvgb1/wp-go/app/wpconfig" "github.com/fthvgb1/wp-go/app/wpconfig"
"strings" "strings"
) )
@ -18,20 +18,17 @@ func Hook(h *wp.Handle) {
} }
func configs(h *wp.Handle) { func configs(h *wp.Handle) {
h.PushComponentFilterFn(widgets.Search, func(h *wp.Handle, s string, args ...any) string { h.AddActionFilter(widgets.Search, func(h *wp.Handle, s string, args ...any) string {
return strings.ReplaceAll(s, `class="search-submit"`, `class="search-submit screen-reader-text"`) return strings.ReplaceAll(s, `class="search-submit"`, `class="search-submit screen-reader-text"`)
}) })
wp.InitPipe(h) wp.InitPipe(h)
h.PushHandler(constraints.PipeMiddleware, constraints.Home, middleware.CommonMiddleware(h)
wp.NewHandleFn(widget.IsCategory, 100, "widget.IsCategory")) setPaginationAndRender(h)
h.Index.SetPageEle(plugins.TwentyFifteenPagination())
h.PushCacheGroupHeadScript(constraints.AllScene, "CalCustomBackGround", 10.005, CalCustomBackGround) h.PushCacheGroupHeadScript(constraints.AllScene, "CalCustomBackGround", 10.005, CalCustomBackGround)
h.PushCacheGroupHeadScript(constraints.AllScene, "colorSchemeCss", 10.0056, colorSchemeCss) h.PushCacheGroupHeadScript(constraints.AllScene, "colorSchemeCss", 10.0056, colorSchemeCss)
h.CommonComponents() h.CommonComponents()
components.WidgetArea(h) components.WidgetArea(h)
wp.ReplyCommentJs(h) h.PushRender(constraints.AllScene, wp.NewHandleFn(renderCustomHeader, 20.5, "renderCustomHeader"))
h.SetData("customHeader", customHeader(h))
wp.PushIndexHandler(constraints.PipeRender, h, wp.NewHandleFn(wp.IndexRender, 50.005, "wp.IndexRender")) wp.PushIndexHandler(constraints.PipeRender, h, wp.NewHandleFn(wp.IndexRender, 50.005, "wp.IndexRender"))
h.PushRender(constraints.Detail, wp.NewHandleFn(wp.DetailRender, 50.005, "wp.DetailRender")) h.PushRender(constraints.Detail, wp.NewHandleFn(wp.DetailRender, 50.005, "wp.DetailRender"))
h.PushRender(constraints.Detail, wp.NewHandleFn(postThumb, 60.005, "postThumb")) h.PushRender(constraints.Detail, wp.NewHandleFn(postThumb, 60.005, "postThumb"))
@ -41,8 +38,26 @@ func configs(h *wp.Handle) {
h.PushRender(constraints.AllScene, wp.NewHandleFn(wp.PreTemplate, 70.005, "wp.PreTemplate")) h.PushRender(constraints.AllScene, wp.NewHandleFn(wp.PreTemplate, 70.005, "wp.PreTemplate"))
} }
func setPaginationAndRender(h *wp.Handle) {
h.PushHandler(constraints.PipeRender, constraints.Detail, wp.NewHandleFn(func(hh *wp.Handle) {
d := hh.GetDetailHandle()
d.CommentRender = plugins.CommentRender()
d.CommentPageEle = plugins.TwentyFifteenCommentPagination()
}, 150, "setPaginationAndRender"))
wp.PushIndexHandler(constraints.PipeRender, h, wp.NewHandleFn(func(hh *wp.Handle) {
i := hh.GetIndexHandle()
i.SetPageEle(plugins.TwentyFifteenPagination())
}, 150, "setPaginationAndRender"))
}
func postThumb(h *wp.Handle) { func postThumb(h *wp.Handle) {
if h.Detail.Post.Thumbnail.Path != "" { d := h.GetDetailHandle()
h.Detail.Post.Thumbnail = wpconfig.Thumbnail(h.Detail.Post.Thumbnail.OriginAttachmentData, "post-thumbnail", "") if d.Post.Thumbnail.Path != "" {
d.Post.Thumbnail = wpconfig.Thumbnail(d.Post.Thumbnail.OriginAttachmentData, "post-thumbnail", "")
} }
} }
func renderCustomHeader(h *wp.Handle) {
h.SetData("customHeader", customHeader(h))
}

View File

@ -1,9 +0,0 @@
{{define "layout/colophon"}}
<footer id="colophon" class="site-footer">
<div class="wrap">
<div class="site-info">
<a href="https://cn.wordpress.org/" class="imprint">自豪地采用WordPress</a>
</div>
</div>
</footer>
{{end}}

View File

@ -64,12 +64,21 @@
{{ if .showComment}} {{ if .showComment}}
<div id="comments" class="comments-area"> <div id="comments" class="comments-area">
{{ if gt .post.CommentCount 0}} {{ if ne .comments ""}}
<h2 class="comments-title">“{{.post.PostTitle}}”的{{.post.CommentCount}}个回复 </h2> <h2 class="comments-title">“{{.post.PostTitle}}”的{{.totalCommentNum}}个回复 </h2>
<ol class="comment-list"> <ol class="comment-list">
{{.comments|unescaped}} {{.comments|unescaped}}
</ol> </ol>
{{if gt .totalCommentPage 1}}
<nav class="navigation comments-pagination" aria-label="评论">
<h2 class="screen-reader-text">评论导航</h2>
<div class="nav-links">
{{ .commentPageNav|unescaped}}
</div><!-- .nav-links -->
</nav>
{{end}}
{{end}} {{end}}
{{if eq .post.CommentStatus "open"}} {{if eq .post.CommentStatus "open"}}
{{template "respond" .}} {{template "respond" .}}
@ -123,7 +132,7 @@
</div> </div>
</div> </div>
{{template "layout/colophon"}} {{template "common/colophon" .}}
</div> </div>

View File

@ -77,6 +77,6 @@
{{end}} {{end}}
</div> </div>
{{template "layout/colophon"}} {{template "common/colophon" .}}
</div> </div>
{{end}} {{end}}

View File

@ -1,8 +1,8 @@
package twentyseventeen package twentyseventeen
import ( import (
"context"
"fmt" "fmt"
"github.com/fthvgb1/wp-go/app/cmd/reload"
"github.com/fthvgb1/wp-go/app/pkg/constraints" "github.com/fthvgb1/wp-go/app/pkg/constraints"
"github.com/fthvgb1/wp-go/app/pkg/constraints/widgets" "github.com/fthvgb1/wp-go/app/pkg/constraints/widgets"
"github.com/fthvgb1/wp-go/app/pkg/logs" "github.com/fthvgb1/wp-go/app/pkg/logs"
@ -10,11 +10,12 @@ import (
"github.com/fthvgb1/wp-go/app/plugins" "github.com/fthvgb1/wp-go/app/plugins"
"github.com/fthvgb1/wp-go/app/theme/wp" "github.com/fthvgb1/wp-go/app/theme/wp"
"github.com/fthvgb1/wp-go/app/theme/wp/components" "github.com/fthvgb1/wp-go/app/theme/wp/components"
"github.com/fthvgb1/wp-go/app/theme/wp/components/widget" "github.com/fthvgb1/wp-go/app/theme/wp/middleware"
"github.com/fthvgb1/wp-go/app/wpconfig" "github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/cache/reload"
"github.com/fthvgb1/wp-go/helper" "github.com/fthvgb1/wp-go/helper"
str "github.com/fthvgb1/wp-go/helper/strings" str "github.com/fthvgb1/wp-go/helper/strings"
"github.com/gin-gonic/gin" "github.com/fthvgb1/wp-go/plugin/pagination"
"strings" "strings"
) )
@ -27,23 +28,33 @@ var paginate = func() plugins.PageEle {
p.NextEle = strings.Replace(p.NextEle, "下一页", `<span class="screen-reader-text">下一页</span> p.NextEle = strings.Replace(p.NextEle, "下一页", `<span class="screen-reader-text">下一页</span>
<svg class="icon icon-arrow-right" aria-hidden="true" role="img"> <use href="#icon-arrow-right" xlink:href="#icon-arrow-right"></use> <svg class="icon icon-arrow-right" aria-hidden="true" role="img"> <use href="#icon-arrow-right" xlink:href="#icon-arrow-right"></use>
</svg>`, 1) </svg>`, 1)
commentPageEle = plugins.PaginationNav{
Currents: p.Current,
Prevs: p.Prev,
Nexts: p.Next,
Dotss: p.Dots,
Middles: p.Middle,
Urlss: plugins.TwentyFifteenCommentPagination().Urls,
}
return p return p
}() }()
var commentPageEle pagination.Render
func Hook(h *wp.Handle) { func Hook(h *wp.Handle) {
wp.Run(h, configs) wp.Run(h, configs)
} }
func configs(h *wp.Handle) { func configs(h *wp.Handle) {
wp.InitPipe(h) wp.InitPipe(h)
h.PushHandler(constraints.PipeMiddleware, constraints.Home, middleware.CommonMiddleware(h)
wp.NewHandleFn(widget.IsCategory, 100.006, "widget.IsCategory")) h.AddActionFilter("bodyClass", calClass)
h.PushComponentFilterFn("bodyClass", calClass)
h.PushCacheGroupHeadScript(constraints.AllScene, "colorScheme-customHeader", 10, colorScheme, customHeader) h.PushCacheGroupHeadScript(constraints.AllScene, "colorScheme-customHeader", 10, colorScheme, customHeader)
components.WidgetArea(h) components.WidgetArea(h)
pushScripts(h) pushScripts(h)
h.PushRender(constraints.AllStats, wp.NewHandleFn(calCustomHeader, 10.005, "calCustomHeader")) h.PushRender(constraints.AllStats, wp.NewHandleFn(calCustomHeader, 10.005, "calCustomHeader"))
h.SetComponentsArgs(widgets.Widget, map[string]string{ wp.SetComponentsArgs(widgets.Widget, map[string]string{
"{$before_widget}": `<section id="%s" class="%s">`, "{$before_widget}": `<section id="%s" class="%s">`,
"{$after_widget}": `</section>`, "{$after_widget}": `</section>`,
}) })
@ -52,12 +63,11 @@ func configs(h *wp.Handle) {
wp.NewHandleFn(errorsHandle, 80.005, "errorsHandle"), wp.NewHandleFn(errorsHandle, 80.005, "errorsHandle"),
) )
videoHeader(h) videoHeader(h)
h.Detail.CommentRender = commentFormat h.SetData("colophon", colophon)
setPaginationAndRender(h)
h.CommonComponents() h.CommonComponents()
h.Index.SetPageEle(paginate)
wp.ReplyCommentJs(h)
h.PushPostPlugin(postThumbnail) h.PushPostPlugin(postThumbnail)
wp.SetComponentsArgsForMap(h, widgets.Search, "{$form}", searchForm) wp.SetComponentsArgsForMap(widgets.Search, "{$form}", searchForm)
wp.PushIndexHandler(constraints.PipeRender, h, wp.NewHandleFn(wp.IndexRender, 10.005, "wp.IndexRender")) wp.PushIndexHandler(constraints.PipeRender, h, wp.NewHandleFn(wp.IndexRender, 10.005, "wp.IndexRender"))
h.PushRender(constraints.Detail, wp.NewHandleFn(wp.DetailRender, 10.005, "wp.DetailRender")) h.PushRender(constraints.Detail, wp.NewHandleFn(wp.DetailRender, 10.005, "wp.DetailRender"))
h.PushDataHandler(constraints.Detail, wp.NewHandleFn(wp.Detail, 100.005, "wp.Detail"), wp.NewHandleFn(postThumb, 90.005, "{theme}.postThumb")) h.PushDataHandler(constraints.Detail, wp.NewHandleFn(wp.Detail, 100.005, "wp.Detail"), wp.NewHandleFn(postThumb, 90.005, "{theme}.postThumb"))
@ -65,6 +75,19 @@ func configs(h *wp.Handle) {
h.PushDataHandler(constraints.AllScene, wp.NewHandleFn(wp.PreCodeAndStats, 90.005, "wp.PreCodeAndStats")) h.PushDataHandler(constraints.AllScene, wp.NewHandleFn(wp.PreCodeAndStats, 90.005, "wp.PreCodeAndStats"))
} }
func setPaginationAndRender(h *wp.Handle) {
h.PushHandler(constraints.PipeRender, constraints.Detail, wp.NewHandleFn(func(hh *wp.Handle) {
d := hh.GetDetailHandle()
d.CommentRender = commentFormat
d.CommentPageEle = commentPageEle
d.CommentStep = 2
}, 150, "setPaginationAndRender"))
wp.PushIndexHandler(constraints.PipeRender, h, wp.NewHandleFn(func(hh *wp.Handle) {
i := hh.GetIndexHandle()
i.SetPageEle(paginate)
}, 150, "setPaginationAndRender"))
}
var searchForm = `<form role="search" method="get" class="search-form" action="/"> var searchForm = `<form role="search" method="get" class="search-form" action="/">
<label for="search-form-1"> <label for="search-form-1">
<span class="screen-reader-text">{$label}</span> <span class="screen-reader-text">{$label}</span>
@ -85,7 +108,7 @@ func errorsHandle(h *wp.Handle) {
} }
func postThumb(h *wp.Handle) { func postThumb(h *wp.Handle) {
d := h.Detail d := h.GetDetailHandle()
if d.Post.Thumbnail.Path != "" { if d.Post.Thumbnail.Path != "" {
img := wpconfig.Thumbnail(d.Post.Thumbnail.OriginAttachmentData, "full", "", "thumbnail", "post-thumbnail") img := wpconfig.Thumbnail(d.Post.Thumbnail.OriginAttachmentData, "full", "", "thumbnail", "post-thumbnail")
img.Sizes = "100vw" img.Sizes = "100vw"
@ -100,20 +123,28 @@ type comment struct {
plugins.CommonCommentFormat plugins.CommonCommentFormat
} }
func (c comment) FormatLi(ctx *gin.Context, m models.Comments, depth int, isTls bool, eo, parent string) string { var commentLi = plugins.CommonLi()
templ := plugins.CommonLi()
templ = strings.ReplaceAll(templ, `<a rel="nofollow" class="comment-reply-link" var respondFn = plugins.Responds(respondStr)
href="/p/{{PostId}}?replytocom={{CommentId}}#respond" data-commentid="{{CommentId}}" data-postid="{{PostId}}"
data-belowelement="div-comment-{{CommentId}}" data-respondelement="respond" func (c comment) FormatLi(_ context.Context, m models.Comments, depth, maxDepth, page int, isTls, isThreadComments bool, eo, parent string) string {
data-replyto="回复给{{CommentAuthor}}" return plugins.FormatLi(commentLi, m, respondFn, depth, maxDepth, page, isTls, isThreadComments, eo, parent)
aria-label="回复给{{CommentAuthor}}">回复</a>`, `<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}}"><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)
} }
var colophon = `<footer id="colophon" class="site-footer">
<div class="wrap">
<div class="site-info">
<a href="https://github.com/fthvgb1/wp-go" class="imprint">自豪地采用 wp-go</a>
</div>
</div>
</footer>`
var respondStr = `<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}}"><svg class="icon icon-mail-reply" aria-hidden="true" role="img"> <use href="#icon-mail-reply" xlink:href="#icon-mail-reply"></use> </svg>回复</a>`
func postThumbnail(h *wp.Handle, posts *models.Posts) { func postThumbnail(h *wp.Handle, posts *models.Posts) {
if posts.Thumbnail.Path != "" { if posts.Thumbnail.Path != "" {
posts.Thumbnail.Sizes = "(max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px" posts.Thumbnail.Sizes = "(max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px"
@ -123,7 +154,7 @@ func postThumbnail(h *wp.Handle, posts *models.Posts) {
} }
} }
var header = reload.Vars(models.PostThumbnail{}) var header = reload.Vars(models.PostThumbnail{}, "twentyseventeen-headerImage")
func calCustomHeader(h *wp.Handle) { func calCustomHeader(h *wp.Handle) {
h.SetData("HeaderImage", getHeaderImage(h)) h.SetData("HeaderImage", getHeaderImage(h))
@ -176,7 +207,7 @@ func calClass(h *wp.Handle, s string, _ ...any) string {
} }
func videoHeader(h *wp.Handle) { func videoHeader(h *wp.Handle) {
h.PushComponentFilterFn("videoSetting", videoPlay) h.AddActionFilter("videoSetting", videoPlay)
wp.CustomVideo(h, constraints.Home) wp.CustomVideo(h, constraints.Home)
} }

View File

@ -33,14 +33,14 @@ func (h *Handle) BodyClass() string {
case constraints.Search: case constraints.Search:
s := "search-no-results" s := "search-no-results"
if len(h.Index.Posts) > 0 { if len(h.GetIndexHandle().Posts) > 0 {
s = "search-results" s = "search-results"
} }
class = append(class, "search", s) class = append(class, "search", s)
case constraints.Category, constraints.Tag: case constraints.Category, constraints.Tag:
class = append(class, "archive", "category") class = append(class, "archive", "category")
cat := h.Index.Param.Category cat := h.GetIndexHandle().Param.Category
if cat == "" { if cat == "" {
break break
} }
@ -54,16 +54,16 @@ func (h *Handle) BodyClass() string {
case constraints.Author: case constraints.Author:
class = append(class, "archive", "author") class = append(class, "archive", "author")
author := h.Index.Param.Author author := h.GetIndexHandle().Param.Author
user, _ := cache.GetUserByName(h.C, author) user, _ := cache.GetUserByName(h.C, author)
class = append(class, str.Join("author-", number.IntToString(user.Id))) class = append(class, str.Join("author-", number.IntToString(user.Id)))
if user.UserLogin[0] != '%' { if user.DisplayName[0] != '%' {
class = append(class, str.Join("author-", user.UserLogin)) class = append(class, str.Join("author-", user.DisplayName))
} }
case constraints.Detail: case constraints.Detail:
class = append(class, "post-template-default", "single", "single-post") class = append(class, "post-template-default", "single", "single-post")
class = append(class, str.Join("postid-", number.IntToString(h.Detail.Post.Id))) class = append(class, str.Join("postid-", number.IntToString(h.GetDetailHandle().Post.Id)))
if len(h.themeMods.ThemeSupport.PostFormats) > 0 { if len(h.themeMods.ThemeSupport.PostFormats) > 0 {
class = append(class, "single-format-standard") class = append(class, "single-format-standard")
} }
@ -77,7 +77,7 @@ func (h *Handle) BodyClass() string {
if h.themeMods.ThemeSupport.ResponsiveEmbeds { if h.themeMods.ThemeSupport.ResponsiveEmbeds {
class = append(class, "wp-embed-responsive") class = append(class, "wp-embed-responsive")
} }
return h.ComponentFilterFnHook("bodyClass", strings.Join(class, " ")) return h.DoActionFilter("bodyClass", strings.Join(class, " "))
} }
func postClass(h *Handle) func(posts models.Posts) string { func postClass(h *Handle) func(posts models.Posts) string {
@ -113,7 +113,7 @@ func (h *Handle) PostClass(posts models.Posts) string {
class = append(class, TermClass(term)) class = append(class, TermClass(term))
} }
return h.ComponentFilterFnHook("postClass", strings.Join(class, " ")) return h.DoActionFilter("postClass", strings.Join(class, " "))
} }
func TermClass(term models.TermsMy) string { func TermClass(term models.TermsMy) string {

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

@ -0,0 +1,126 @@
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.Comments]("postCommentData")
children, _ := cachemanager.GetMapCache[uint64, []uint64]("commentChildren")
h := CommentHandle{
maxDepth: str.ToInteger(wpconfig.GetOption("thread_comments_depth"), 5),
depth: 1,
isTls: isTLS,
html: render,
order: wpconfig.GetOption("comment_order"),
ca: ca,
children: children,
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.Comments]
children *cache.MapCache[uint64, []uint64]
threadComments bool
}
func (c CommentHandle) findGrandchildComments(ctx context.Context, timeout time.Duration, comments []models.Comments) ([]models.Comments, error) {
parentIds := slice.Map(comments, func(t models.Comments) uint64 {
return t.CommentId
})
children, err := c.children.GetCacheBatch(ctx, parentIds, timeout)
if err != nil {
return nil, err
}
rr := slice.FilterAndMap(children, func(t []uint64) ([]uint64, bool) {
return t, len(t) > 0
})
if len(rr) < 1 {
return comments, nil
}
ids := slice.Decompress(rr)
r, err := c.ca.GetCacheBatch(ctx, ids, timeout)
if err != nil {
return nil, err
}
rrr, err := c.findGrandchildComments(ctx, timeout, r)
if err != nil {
return nil, err
}
comments = append(comments, rrr...)
slice.Sort(comments, func(i, j models.Comments) bool {
return c.html.FloorOrder(i, j)
})
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 > 1 && c.depth < c.maxDepth {
slice.Sort(comments, func(i, j models.Comments) bool {
return c.html.FloorOrder(i, j)
})
}
fixChildren := false
if c.depth >= c.maxDepth {
comments, err = c.findGrandchildComments(ctx, timeout, comments)
if err != nil {
return "", err
}
fixChildren = true
}
s := str.NewBuilder()
for i, comment := range comments {
eo := "even"
if (i+1)%2 == 0 {
eo = "odd"
}
parent := ""
fl := false
var children []uint64
if !fixChildren {
children, err = c.children.GetCache(ctx, comment.CommentId, timeout)
if err != nil {
return "", err
}
}
if c.threadComments && len(children) > 0 && c.depth < c.maxDepth+1 {
parent = "parent"
fl = true
}
s.WriteString(c.html.FormatLi(ctx, comment, c.depth, c.maxDepth, c.page, c.isTls, c.threadComments, eo, parent))
if fl {
c.depth++
ss, err := c.formatComments(ctx, 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

@ -1,62 +1,103 @@
package wp package wp
import ( import (
"github.com/fthvgb1/wp-go/app/cmd/reload"
"github.com/fthvgb1/wp-go/app/pkg/constraints" "github.com/fthvgb1/wp-go/app/pkg/constraints"
"github.com/fthvgb1/wp-go/cache/reload"
"github.com/fthvgb1/wp-go/helper/maps" "github.com/fthvgb1/wp-go/helper/maps"
"github.com/fthvgb1/wp-go/helper/slice" "github.com/fthvgb1/wp-go/helper/slice"
str "github.com/fthvgb1/wp-go/helper/strings" str "github.com/fthvgb1/wp-go/helper/strings"
"github.com/fthvgb1/wp-go/safety"
"strings" "strings"
) )
func (h *Handle) DeleteComponents(scene, name string) { var handleComponents = safety.NewMap[string, map[string][]Components[string]]()
h.componentHook[scene] = append(h.componentHook[scene], func(c Components[string]) (Components[string], bool) { var handleComponentHook = safety.NewMap[string, map[string][]func(Components[string]) (Components[string], bool)]()
return c, c.Name != name
var componentsArgs = safety.NewMap[string, any]()
var componentFilterFns = safety.NewMap[string, []func(*Handle, string, ...any) string]()
func (h *Handle) DeleteComponents(scene, componentKey, componentName string) {
v, ok := handleComponentHook.Load(scene)
if !ok {
v = make(map[string][]func(Components[string]) (Components[string], bool))
}
v[componentKey] = append(v[componentKey], func(c Components[string]) (Components[string], bool) {
return c, c.Name != componentName
}) })
handleComponentHook.Store(scene, v)
} }
func (h *Handle) ReplaceComponents(scene, name string, components Components[string]) { func (h *Handle) ReplaceComponents(scene, componentKey, componentName string, components Components[string]) {
h.componentHook[scene] = append(h.componentHook[scene], func(c Components[string]) (Components[string], bool) { v, ok := handleComponentHook.Load(scene)
if c.Name == name { if !ok {
v = make(map[string][]func(Components[string]) (Components[string], bool))
}
v[componentKey] = append(v[componentKey], func(c Components[string]) (Components[string], bool) {
if c.Name == componentName {
c = components c = components
} }
return c, true return c, true
}) })
handleComponentHook.Store(scene, v)
} }
func (h *Handle) HookComponents(scene string, fn func(Components[string]) (Components[string], bool)) { func (h *Handle) PushComponentHooks(scene, componentKey string, fn func(Components[string]) (Components[string], bool)) {
h.componentHook[scene] = append(h.componentHook[scene], fn) v, ok := handleComponentHook.Load(scene)
if !ok {
v = make(map[string][]func(Components[string]) (Components[string], bool))
}
v[componentKey] = append(v[componentKey], fn)
handleComponentHook.Store(scene, v)
} }
func CalComponents(h *Handle) { var GetAndHookComponents = reload.BuildMapFnWithAnyParams[string]("calComponents", HookComponent)
componentss := reload.GetAnyValMapBy("scene-components", str.Join("allScene-", h.scene), h, func(h *Handle) map[string][]Components[string] {
return maps.MergeBy(func(k string, v1, v2 []Components[string]) ([]Components[string], bool) { func HookComponent(a ...any) []Components[string] {
vv := append(v1, v2...) componentKey := a[0].(string)
return vv, vv != nil scene := a[1].(string)
}, nil, h.components[h.scene], h.components[constraints.AllScene]) mut := reload.GetGlobeMutex()
mut.Lock()
defer mut.Unlock()
components := GetComponents(scene, componentKey)
r := slice.FilterAndMap(components, func(component Components[string]) (Components[string], bool) {
keyHooks, ok := handleComponentHook.Load(scene)
if !ok {
return component, true
}
hooks := keyHooks[componentKey]
for _, fn := range hooks {
hookedComponent, ok := fn(component)
if !ok { // DeleteComponents fn
return hookedComponent, false
}
component = hookedComponent // ReplaceComponents fn
}
return component, true
}) })
for k, components := range componentss { slice.SimpleSort(r, slice.DESC, func(t Components[string]) float64 {
key := str.Join("calComponents-", h.scene, "-", k) return t.Order
ss := reload.GetAnyValMapBy("calComponents", key, h, func(h *Handle) []Components[string] { })
r := slice.FilterAndMap(components, func(t Components[string]) (Components[string], bool) { return r
fns, ok := h.componentHook[k] }
if !ok {
return t, true func GetComponents(scene, key string) (r []Components[string]) {
} sceneComponents, _ := handleComponents.Load(scene)
for _, fn := range fns { allSceneComponents, _ := handleComponents.Load(constraints.AllScene)
c, ok := fn(t) r = append(sceneComponents[key], allSceneComponents[key]...)
if !ok { return
return c, false }
}
t = c var CacheComponent = reload.BuildMapFnWithAnyParams[string]("cacheComponents", cacheComponentFn)
}
return t, true func cacheComponentFn(a ...any) string {
}) return a[0].(Components[string]).Fn(a[1].(*Handle))
slice.Sort(r, func(i, j Components[string]) bool { }
return i.Order > j.Order
}) func CalComponent(h *Handle) func(string) string {
return r return func(componentKey string) string {
}) cacheKey := str.Join("get-hook-Components-", h.scene, "-", componentKey)
var s = make([]string, 0, len(ss)) hookedComponents := GetAndHookComponents(cacheKey, componentKey, h.scene)
for _, component := range ss { var s = make([]string, 0, len(hookedComponents))
for _, component := range hookedComponents {
if component.Val != "" { if component.Val != "" {
s = append(s, component.Val) s = append(s, component.Val)
continue continue
@ -64,7 +105,8 @@ func CalComponents(h *Handle) {
if component.Fn != nil { if component.Fn != nil {
v := "" v := ""
if component.Cached { if component.Cached {
v = reload.GetAnyValMapBy("cacheComponents", component.Name, h, component.Fn) key := str.Join(h.scene, "-", componentKey, "-", component.Name)
v = CacheComponent(key, component, h)
} else { } else {
v = component.Fn(h) v = component.Fn(h)
} }
@ -73,17 +115,17 @@ func CalComponents(h *Handle) {
} }
} }
} }
h.ginH[k] = strings.Join(s, "\n") return strings.Join(s, "\n")
} }
} }
func (h *Handle) PushComponents(scene, componentType string, components ...Components[string]) { func (h *Handle) PushComponents(scene, componentType string, components ...Components[string]) {
c, ok := h.components[scene] c, ok := handleComponents.Load(scene)
if !ok { if !ok {
c = make(map[string][]Components[string]) c = make(map[string][]Components[string])
h.components[scene] = c
} }
c[componentType] = append(c[componentType], components...) c[componentType] = append(c[componentType], components...)
handleComponents.Store(scene, c)
} }
func (h *Handle) PushGroupComponentStr(scene, componentType, name string, order float64, strs ...string) { func (h *Handle) PushGroupComponentStr(scene, componentType, name string, order float64, strs ...string) {
@ -139,8 +181,8 @@ func (h *Handle) PushGroupHeadScript(scene, name string, order float64, str ...s
h.PushGroupComponentStr(scene, constraints.HeadScript, name, order, str...) h.PushGroupComponentStr(scene, constraints.HeadScript, name, order, str...)
} }
func GetComponentsArgs[T any](h *Handle, k string, defaults T) T { func GetComponentsArgs[T any](k string, defaults T) T {
v, ok := h.componentsArgs[k] v, ok := componentsArgs.Load(k)
if ok { if ok {
vv, ok := v.(T) vv, ok := v.(T)
if ok { if ok {
@ -150,60 +192,61 @@ func GetComponentsArgs[T any](h *Handle, k string, defaults T) T {
return defaults return defaults
} }
func PushComponentsArgsForSlice[T any](h *Handle, name string, v ...T) { func PushComponentsArgsForSlice[T any](name string, v ...T) {
val, ok := h.componentsArgs[name] val, ok := componentsArgs.Load(name)
if !ok { if !ok {
var vv []T var vv []T
vv = append(vv, v...) vv = append(vv, v...)
h.componentsArgs[name] = vv componentsArgs.Store(name, vv)
return return
} }
vv, ok := val.([]T) vv, ok := val.([]T)
if ok { if ok {
vv = append(vv, v...) vv = append(vv, v...)
h.componentsArgs[name] = vv componentsArgs.Store(name, vv)
} }
} }
func SetComponentsArgsForMap[K comparable, V any](h *Handle, name string, key K, v V) { func SetComponentsArgsForMap[K comparable, V any](name string, key K, v V) {
val, ok := h.componentsArgs[name] val, ok := componentsArgs.Load(name)
if !ok { if !ok {
vv := make(map[K]V) vv := make(map[K]V)
vv[key] = v vv[key] = v
h.componentsArgs[name] = vv componentsArgs.Store(name, vv)
return return
} }
vv, ok := val.(map[K]V) vv, ok := val.(map[K]V)
if ok { if ok {
vv[key] = v vv[key] = v
h.componentsArgs[name] = vv componentsArgs.Store(name, vv)
} }
} }
func MergeComponentsArgsForMap[K comparable, V any](h *Handle, name string, m map[K]V) { func MergeComponentsArgsForMap[K comparable, V any](name string, m map[K]V) {
val, ok := h.componentsArgs[name] val, ok := componentsArgs.Load(name)
if !ok { if !ok {
h.componentsArgs[name] = m componentsArgs.Store(name, m)
return return
} }
vv, ok := val.(map[K]V) vv, ok := val.(map[K]V)
if ok { if ok {
h.componentsArgs[name] = maps.Merge(vv, m) componentsArgs.Store(name, maps.Merge(vv, m))
} }
} }
func (h *Handle) SetComponentsArgs(key string, value any) { func SetComponentsArgs(key string, value any) {
h.componentsArgs[key] = value componentsArgs.Store(key, value)
} }
func (h *Handle) ComponentFilterFn(name string) ([]func(*Handle, string, ...any) string, bool) { func (h *Handle) GetComponentFilterFn(name string) ([]func(*Handle, string, ...any) string, bool) {
fn, ok := h.componentFilterFn[name] return componentFilterFns.Load(name)
return fn, ok
} }
func (h *Handle) PushComponentFilterFn(name string, fns ...func(*Handle, string, ...any) string) { func (h *Handle) AddActionFilter(name string, fns ...func(*Handle, string, ...any) string) {
h.componentFilterFn[name] = append(h.componentFilterFn[name], fns...) v, _ := componentFilterFns.Load(name)
v = append(v, fns...)
componentFilterFns.Store(name, v)
} }
func (h *Handle) ComponentFilterFnHook(name, s string, args ...any) string { func (h *Handle) DoActionFilter(name, s string, args ...any) string {
calls, ok := h.componentFilterFn[name] calls, ok := componentFilterFns.Load(name)
if ok { if ok {
return slice.Reduce(calls, func(fn func(*Handle, string, ...any) string, r string) string { return slice.Reduce(calls, func(fn func(*Handle, string, ...any) string, r string) string {
return fn(h, r, args...) return fn(h, r, args...)

View File

@ -1,6 +1,7 @@
package components package components
import ( import (
"github.com/fthvgb1/wp-go/app/pkg/logs"
"github.com/fthvgb1/wp-go/app/theme/wp" "github.com/fthvgb1/wp-go/app/theme/wp"
"github.com/fthvgb1/wp-go/app/theme/wp/components/block" "github.com/fthvgb1/wp-go/app/theme/wp/components/block"
"github.com/fthvgb1/wp-go/app/wpconfig" "github.com/fthvgb1/wp-go/app/wpconfig"
@ -29,6 +30,7 @@ func Block(id string) (func(*wp.Handle) string, string) {
if ok { if ok {
s, err := fn(h, id, parserBlock) s, err := fn(h, id, parserBlock)
if err != nil { if err != nil {
logs.Error(err, str.Join("parse block", parserBlock.Name, " fail "), parserBlock)
continue continue
} }
out = append(out, s()) out = append(out, s())

View File

@ -4,7 +4,6 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"github.com/fthvgb1/wp-go/app/cmd/reload"
"github.com/fthvgb1/wp-go/app/pkg/cache" "github.com/fthvgb1/wp-go/app/pkg/cache"
constraints2 "github.com/fthvgb1/wp-go/app/pkg/constraints" constraints2 "github.com/fthvgb1/wp-go/app/pkg/constraints"
"github.com/fthvgb1/wp-go/app/pkg/constraints/widgets" "github.com/fthvgb1/wp-go/app/pkg/constraints/widgets"
@ -12,6 +11,7 @@ import (
"github.com/fthvgb1/wp-go/app/pkg/models" "github.com/fthvgb1/wp-go/app/pkg/models"
"github.com/fthvgb1/wp-go/app/theme/wp" "github.com/fthvgb1/wp-go/app/theme/wp"
"github.com/fthvgb1/wp-go/app/theme/wp/components/widget" "github.com/fthvgb1/wp-go/app/theme/wp/components/widget"
"github.com/fthvgb1/wp-go/cache/reload"
"github.com/fthvgb1/wp-go/helper/maps" "github.com/fthvgb1/wp-go/helper/maps"
"github.com/fthvgb1/wp-go/helper/number" "github.com/fthvgb1/wp-go/helper/number"
str "github.com/fthvgb1/wp-go/helper/strings" str "github.com/fthvgb1/wp-go/helper/strings"
@ -50,7 +50,7 @@ func parseAttr(attr map[any]any) string {
} }
style := maps.GetAnyAnyValWithDefaults[map[any]any](attr, nil, "style", "typography") style := maps.GetAnyAnyValWithDefaults[map[any]any](attr, nil, "style", "typography")
if len(style) > 0 { if len(style) > 0 {
styless := maps.AnyAnyMap(style, func(k, v any) (string, string, bool) { styless := maps.AnyAnyMapTo(style, func(k, v any) (string, string, bool) {
kk, ok := k.(string) kk, ok := k.(string)
if !ok { if !ok {
return "", "", false return "", "", false
@ -71,48 +71,61 @@ func parseAttr(attr map[any]any) string {
return strings.Join(attrs, " ") return strings.Join(attrs, " ")
} }
var GetCategoryAttr = reload.BuildValFn("block-category-attr", parseAttr)
var GetCategoryConf = reload.BuildValFnWithConfirm("block-category-conf", categoryConfFn, 5)
func categoryConfFn(blockParser ParserBlock) (map[any]any, bool) {
var con any
err := json.Unmarshal([]byte(blockParser.Attrs), &con)
if err != nil {
logs.Error(err, "解析category attr错误", blockParser.Attrs)
return nil, false
}
var conf map[any]any
switch con.(type) {
case map[any]any:
conf = con.(map[any]any)
case map[string]any:
conf = maps.StrAnyToAnyAny(con.(map[string]any))
}
conf = maps.FilterZeroMerge(categoryConf(), conf)
if maps.GetAnyAnyValWithDefaults(conf, false, "showPostCounts") {
conf["count"] = int64(1)
}
if maps.GetAnyAnyValWithDefaults(conf, false, "displayAsDropdown") {
conf["dropdown"] = int64(1)
}
if maps.GetAnyAnyValWithDefaults(conf, false, "showHierarchy") {
conf["hierarchical"] = int64(1)
}
class := maps.GetAnyAnyValWithDefaults(conf, "", "className")
classes := strings.Split(class, " ")
classes = append(classes, "wp-block-categories")
if conf["dropdown"].(int64) == 1 {
classes = append(classes, "wp-block-categories-dropdown")
conf["className"] = strings.Join(classes, " ")
} else {
classes = append(classes, "wp-block-categories-list")
conf["className"] = strings.Join(classes, " ")
}
return conf, true
}
var GetCategoryArgs = reload.BuildValFnWithAnyParams("block-category-args", categoryArgs)
func categoryArgs(_ ...any) map[string]string {
args := wp.GetComponentsArgs(widgets.Widget, map[string]string{})
return maps.FilterZeroMerge(categoryDefaultArgs(), args)
}
func Category(h *wp.Handle, id string, blockParser ParserBlock) (func() string, error) { func Category(h *wp.Handle, id string, blockParser ParserBlock) (func() string, error) {
counter := number.Counters[int]() counter := number.Counters[int]()
var err error var err error
conf := reload.GetAnyValBys("block-category-conf", h, func(h *wp.Handle) map[any]any { conf := GetCategoryConf(blockParser)
var con any
err = json.Unmarshal([]byte(blockParser.Attrs), &con)
if err != nil {
logs.Error(err, "解析category attr错误", blockParser.Attrs)
return nil
}
var conf map[any]any
switch con.(type) {
case map[any]any:
conf = con.(map[any]any)
case map[string]any:
conf = maps.StrAnyToAnyAny(con.(map[string]any))
}
conf = maps.FilterZeroMerge(categoryConf(), conf)
if maps.GetAnyAnyValWithDefaults(conf, false, "showPostCounts") {
conf["count"] = int64(1)
}
if maps.GetAnyAnyValWithDefaults(conf, false, "displayAsDropdown") {
conf["dropdown"] = int64(1)
}
if maps.GetAnyAnyValWithDefaults(conf, false, "showHierarchy") {
conf["hierarchical"] = int64(1)
}
class := maps.GetAnyAnyValWithDefaults(conf, "", "className")
classes := strings.Split(class, " ")
classes = append(classes, "wp-block-categories")
if conf["dropdown"].(int64) == 1 {
classes = append(classes, "wp-block-categories-dropdown")
conf["className"] = strings.Join(classes, " ")
} else {
classes = append(classes, "wp-block-categories-list")
conf["className"] = strings.Join(classes, " ")
}
return conf
})
if err != nil { if err != nil {
return nil, err return nil, err
@ -127,10 +140,7 @@ func Category(h *wp.Handle, id string, blockParser ParserBlock) (func() string,
if maps.GetAnyAnyValWithDefaults(conf, false, "showOnlyTopLevel") { if maps.GetAnyAnyValWithDefaults(conf, false, "showOnlyTopLevel") {
h.C.Set("showOnlyTopLevel", true) h.C.Set("showOnlyTopLevel", true)
} }
args := reload.GetAnyValBys("block-category-args", h, func(h *wp.Handle) map[string]string { args := GetCategoryArgs()
args := wp.GetComponentsArgs(h, widgets.Widget, map[string]string{})
return maps.FilterZeroMerge(categoryDefaultArgs(), args)
})
return func() string { return func() string {
return category(h, id, counter, args, conf) return category(h, id, counter, args, conf)
@ -150,21 +160,21 @@ func category(h *wp.Handle, id string, counter number.Counter[int], args map[str
return str.Join(before, out, args["{$after_widget}"]) return str.Join(before, out, args["{$after_widget}"])
} }
func categoryUl(h *wp.Handle, categories []models.TermsMy, conf map[any]any) string { func categoryUl(h *wp.Handle, categories []models.TermsMy, confAttr map[any]any) string {
s := str.NewBuilder() s := str.NewBuilder()
li := widget.CategoryLi(h, conf, categories) li := widget.CategoryLi(h, confAttr, categories)
attrs := reload.GetAnyValBys("block-category-attr", conf, parseAttr) attrs := GetCategoryAttr(confAttr)
s.Sprintf(`<ul %s>%s</ul>`, attrs, li) s.Sprintf(`<ul %s>%s</ul>`, attrs, li)
return s.String() return s.String()
} }
func dropdown(h *wp.Handle, categories []models.TermsMy, id int, args map[string]string, conf map[any]any) string { func dropdown(h *wp.Handle, categories []models.TermsMy, id int, args map[string]string, confAttr map[any]any) string {
s := str.NewBuilder() s := str.NewBuilder()
ids := fmt.Sprintf(`wp-block-categories-%v`, id) ids := fmt.Sprintf(`wp-block-categories-%v`, id)
args = maps.Copy(args) args = maps.Copy(args)
args["{$selectId}"] = ids args["{$selectId}"] = ids
attrs := reload.GetAnyValBys("block-category-attr", conf, parseAttr) attrs := GetCategoryAttr(confAttr)
selects := widget.DropdownCategories(h, args, conf, categories) selects := widget.DropdownCategories(h, args, confAttr, categories)
s.Sprintf(`<div %s><label class="screen-reader-text" for="%s">%s</label>%s%s</div>`, attrs, ids, args["{$title}"], selects, strings.ReplaceAll(categoryDropdownScript, "{$id}", ids)) s.Sprintf(`<div %s><label class="screen-reader-text" for="%s">%s</label>%s%s</div>`, attrs, ids, args["{$title}"], selects, strings.ReplaceAll(categoryDropdownScript, "{$id}", ids))
return s.String() return s.String()
} }

View File

@ -2,11 +2,11 @@ package widget
import ( import (
"fmt" "fmt"
"github.com/fthvgb1/wp-go/app/cmd/reload"
"github.com/fthvgb1/wp-go/app/pkg/cache" "github.com/fthvgb1/wp-go/app/pkg/cache"
"github.com/fthvgb1/wp-go/app/pkg/constraints/widgets" "github.com/fthvgb1/wp-go/app/pkg/constraints/widgets"
"github.com/fthvgb1/wp-go/app/pkg/models" "github.com/fthvgb1/wp-go/app/pkg/models"
"github.com/fthvgb1/wp-go/app/theme/wp" "github.com/fthvgb1/wp-go/app/theme/wp"
"github.com/fthvgb1/wp-go/cache/reload"
"github.com/fthvgb1/wp-go/helper/maps" "github.com/fthvgb1/wp-go/helper/maps"
"github.com/fthvgb1/wp-go/helper/slice" "github.com/fthvgb1/wp-go/helper/slice"
str "github.com/fthvgb1/wp-go/helper/strings" str "github.com/fthvgb1/wp-go/helper/strings"
@ -40,21 +40,29 @@ var archivesConfig = map[any]any{
"title": "归档", "title": "归档",
} }
var GetArchiveConf = BuildconfigFn(archivesConfig, "widget_archives", int64(2))
var GetArchiveArgs = reload.BuildValFnWithAnyParams("widget_archive-args", archiveArgsFn)
func archiveArgsFn(a ...any) map[string]string {
h := a[0].(*wp.Handle)
conf := a[1].(map[any]any)
id := a[2].(string)
archiveArgs := archiveArgs()
commonArgs := wp.GetComponentsArgs(widgets.Widget, CommonArgs())
args := wp.GetComponentsArgs(widgets.Archive, archiveArgs)
args = maps.FilterZeroMerge(archiveArgs, CommonArgs(), commonArgs, args)
args["{$before_widget}"] = fmt.Sprintf(args["{$before_widget}"], str.Join("archives-", id), str.Join("widget widget_", "archive"))
args["{$title}"] = str.Join(args["{$before_title}"], conf["title"].(string), args["{$after_title}"])
if conf["dropdown"].(int64) == 0 && slice.IsContained(h.CommonThemeMods().ThemeSupport.HTML5, "navigation-widgets") {
args["{$nav}"] = fmt.Sprintf(`<nav aria-label="%s">`, conf["title"].(string))
args["{$navCloser}"] = "</nav>"
}
return args
}
func Archive(h *wp.Handle, id string) string { func Archive(h *wp.Handle, id string) string {
conf := configs(archivesConfig, "widget_archives", int64(2)) conf := GetArchiveConf()
args := reload.GetAnyValBys("widget-archive-args", h, func(h *wp.Handle) map[string]string { args := GetArchiveArgs(h, conf, id)
archiveArgs := archiveArgs()
commonArgs := wp.GetComponentsArgs(h, widgets.Widget, CommonArgs())
args := wp.GetComponentsArgs(h, widgets.Archive, archiveArgs)
args = maps.FilterZeroMerge(archiveArgs, CommonArgs(), commonArgs, args)
args["{$before_widget}"] = fmt.Sprintf(args["{$before_widget}"], str.Join("archives-", id), str.Join("widget widget_", "archive"))
args["{$title}"] = str.Join(args["{$before_title}"], conf["title"].(string), args["{$after_title}"])
if conf["dropdown"].(int64) == 0 && slice.IsContained(h.CommonThemeMods().ThemeSupport.HTML5, "navigation-widgets") {
args["{$nav}"] = fmt.Sprintf(`<nav aria-label="%s">`, conf["title"].(string))
args["{$navCloser}"] = "</nav>"
}
return args
})
s := archiveTemplate s := archiveTemplate
if int64(1) == conf["dropdown"].(int64) { if int64(1) == conf["dropdown"].(int64) {
@ -62,7 +70,7 @@ func Archive(h *wp.Handle, id string) string {
} else { } else {
s = strings.ReplaceAll(s, "{$html}", archiveUl(h, conf, args, cache.Archives(h.C))) s = strings.ReplaceAll(s, "{$html}", archiveUl(h, conf, args, cache.Archives(h.C)))
} }
return h.ComponentFilterFnHook(widgets.Archive, str.Replace(s, args)) return h.DoActionFilter(widgets.Archive, str.Replace(s, args))
} }
var dropdownScript = ` var dropdownScript = `
@ -83,11 +91,12 @@ var dropdownScript = `
func archiveDropDown(h *wp.Handle, conf map[any]any, args map[string]string, archives []models.PostArchive) string { func archiveDropDown(h *wp.Handle, conf map[any]any, args map[string]string, archives []models.PostArchive) string {
option := str.NewBuilder() option := str.NewBuilder()
option.Sprintf(`<option value="">%s</option>`, args["{$dropdown_label}"]) option.Sprintf(`<option value="">%s</option>`, args["{$dropdown_label}"])
month := strings.TrimLeft(h.Index.Param.Month, "0") i := h.GetIndexHandle()
month := strings.TrimLeft(i.Param.Month, "0")
showCount := conf["count"].(int64) showCount := conf["count"].(int64)
for _, archive := range archives { for _, archive := range archives {
sel := "" sel := ""
if h.Index.Param.Year == archive.Year && month == archive.Month { if i.Param.Year == archive.Year && month == archive.Month {
sel = "selected" sel = "selected"
} }
count := "" count := ""

View File

@ -2,17 +2,16 @@ package widget
import ( import (
"fmt" "fmt"
"github.com/fthvgb1/wp-go/app/cmd/reload"
"github.com/fthvgb1/wp-go/app/pkg/cache" "github.com/fthvgb1/wp-go/app/pkg/cache"
"github.com/fthvgb1/wp-go/app/pkg/constraints" "github.com/fthvgb1/wp-go/app/pkg/constraints"
"github.com/fthvgb1/wp-go/app/pkg/constraints/widgets" "github.com/fthvgb1/wp-go/app/pkg/constraints/widgets"
"github.com/fthvgb1/wp-go/app/pkg/models" "github.com/fthvgb1/wp-go/app/pkg/models"
"github.com/fthvgb1/wp-go/app/theme/wp" "github.com/fthvgb1/wp-go/app/theme/wp"
"github.com/fthvgb1/wp-go/cache/reload"
"github.com/fthvgb1/wp-go/helper/maps" "github.com/fthvgb1/wp-go/helper/maps"
"github.com/fthvgb1/wp-go/helper/slice" "github.com/fthvgb1/wp-go/helper/slice"
str "github.com/fthvgb1/wp-go/helper/strings" str "github.com/fthvgb1/wp-go/helper/strings"
"github.com/fthvgb1/wp-go/helper/tree" "github.com/fthvgb1/wp-go/helper/tree"
"net/http"
"strings" "strings"
) )
@ -45,22 +44,29 @@ func categoryArgs() map[string]string {
} }
} }
var GetCategoryConf = BuildconfigFn(categoryConfig, "widget_categories", int64(2))
var GetCategoryArgs = reload.BuildValFnWithAnyParams("widget-category-args", categoryArgsFn)
func categoryArgsFn(a ...any) map[string]string {
h := a[0].(*wp.Handle)
conf := a[1].(map[any]any)
id := a[2].(string)
commonArgs := wp.GetComponentsArgs(widgets.Widget, map[string]string{})
args := wp.GetComponentsArgs(widgets.Categories, categoryArgs())
args = maps.FilterZeroMerge(categoryArgs(), CommonArgs(), commonArgs, args)
args["{$before_widget}"] = fmt.Sprintf(args["{$before_widget}"], str.Join("categories-", id), str.Join("widget widget_", "categories"))
args["{$title}"] = str.Join(args["{$before_title}"], conf["title"].(string), args["{$after_title}"])
if conf["dropdown"].(int64) == 0 && slice.IsContained(h.CommonThemeMods().ThemeSupport.HTML5, "navigation-widgets") {
args["{$nav}"] = fmt.Sprintf(`<nav aria-label="%s">`, args["{title}"])
args["{$navCloser}"] = "</nav>"
}
return args
}
func Category(h *wp.Handle, id string) string { func Category(h *wp.Handle, id string) string {
conf := configs(categoryConfig, "widget_categories", int64(2)) conf := GetCategoryConf()
args := GetCategoryArgs(h, conf, id)
args := reload.GetAnyValBys("widget-category-args", h, func(h *wp.Handle) map[string]string {
commonArgs := wp.GetComponentsArgs(h, widgets.Widget, map[string]string{})
args := wp.GetComponentsArgs(h, widgets.Categories, categoryArgs())
args = maps.FilterZeroMerge(categoryArgs(), CommonArgs(), commonArgs, args)
args["{$before_widget}"] = fmt.Sprintf(args["{$before_widget}"], str.Join("categories-", id), str.Join("widget widget_", "categories"))
args["{$title}"] = str.Join(args["{$before_title}"], conf["title"].(string), args["{$after_title}"])
if conf["dropdown"].(int64) == 0 && slice.IsContained(h.CommonThemeMods().ThemeSupport.HTML5, "navigation-widgets") {
args["{$nav}"] = fmt.Sprintf(`<nav aria-label="%s">`, args["{title}"])
args["{$navCloser}"] = "</nav>"
}
return args
})
t := categoryTemplate t := categoryTemplate
dropdown := conf["dropdown"].(int64) dropdown := conf["dropdown"].(int64)
categories := cache.CategoriesTags(h.C, constraints.Category) categories := cache.CategoriesTags(h.C, constraints.Category)
@ -69,7 +75,7 @@ func Category(h *wp.Handle, id string) string {
} else { } else {
t = strings.ReplaceAll(t, "{$html}", categoryUL(h, args, conf, categories)) t = strings.ReplaceAll(t, "{$html}", categoryUL(h, args, conf, categories))
} }
return h.ComponentFilterFnHook(widgets.Categories, str.Replace(t, args)) return h.DoActionFilter(widgets.Categories, str.Replace(t, args))
} }
func CategoryLi(h *wp.Handle, conf map[any]any, categories []models.TermsMy) string { func CategoryLi(h *wp.Handle, conf map[any]any, categories []models.TermsMy) string {
@ -202,8 +208,9 @@ func DropdownCategories(h *wp.Handle, args map[string]string, conf map[any]any,
s.Sprintf(` <option value="-1">%s</option> s.Sprintf(` <option value="-1">%s</option>
`, args["{$show_option_none}"]) `, args["{$show_option_none}"])
currentCategory := "" currentCategory := ""
i := h.GetIndexHandle()
if h.Scene() == constraints.Category { if h.Scene() == constraints.Category {
currentCategory = h.Index.Param.Category currentCategory = i.Param.Category
} }
showCount := conf["count"].(int64) showCount := conf["count"].(int64)
fn := func(category models.TermsMy, deep int) { fn := func(category models.TermsMy, deep int) {
@ -232,19 +239,11 @@ func DropdownCategories(h *wp.Handle, args map[string]string, conf map[any]any,
}) })
} }
s.WriteString(" </select>\n") s.WriteString(" </select>\n")
return h.ComponentFilterFnHook("wp_dropdown_cats", s.String()) return h.DoActionFilter("wp_dropdown_cats", s.String())
} }
func IsCategory(h *wp.Handle) { func IsCategory(h *wp.Handle) (category models.TermsMy, r bool) {
name, ok := parseDropdownCate(h) cate := wp.GetComponentsArgs[map[string]string](widgets.Categories, categoryArgs())
if ok {
h.C.Redirect(http.StatusMovedPermanently, fmt.Sprintf("/p/category/%s", name))
h.Abort()
}
}
func parseDropdownCate(h *wp.Handle) (cateName string, r bool) {
cate := wp.GetComponentsArgs[map[string]string](h, widgets.Categories, categoryArgs())
name, ok := cate["{$name}"] name, ok := cate["{$name}"]
if !ok || name == "" { if !ok || name == "" {
return return
@ -264,6 +263,15 @@ func parseDropdownCate(h *wp.Handle) (cateName string, r bool) {
return return
} }
r = true r = true
cateName = cc.Name category = cc
return return
} }
func CategoryQueryName(h *wp.Handle) string {
cate := wp.GetComponentsArgs[map[string]string](widgets.Categories, categoryArgs())
name, ok := cate["{$name}"]
if ok {
return name
}
return ""
}

View File

@ -1,9 +1,9 @@
package widget package widget
import ( import (
"github.com/fthvgb1/wp-go/app/cmd/reload"
"github.com/fthvgb1/wp-go/app/theme/wp" "github.com/fthvgb1/wp-go/app/theme/wp"
"github.com/fthvgb1/wp-go/app/wpconfig" "github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/cache/reload"
"github.com/fthvgb1/wp-go/helper/maps" "github.com/fthvgb1/wp-go/helper/maps"
str "github.com/fthvgb1/wp-go/helper/strings" str "github.com/fthvgb1/wp-go/helper/strings"
) )
@ -14,9 +14,13 @@ func Fn(id string, fn func(*wp.Handle, string) string) func(h *wp.Handle) string
} }
} }
func configs[M ~map[K]V, K comparable, V any](m M, key string, a ...any) M { func configFns[K comparable, V any](m map[K]V, key string, a ...any) func(_ ...any) map[K]V {
return reload.GetAnyValBys(str.Join("widget-config-", key), key, func(_ string) M { return func(_ ...any) map[K]V {
c := wpconfig.GetPHPArrayVal[M](key, nil, a...) c := wpconfig.GetPHPArrayVal[map[K]V](key, nil, a...)
return maps.FilterZeroMerge(maps.Copy(m), c) return maps.FilterZeroMerge(maps.Copy(m), c)
}) }
}
func BuildconfigFn[K comparable, V any](m map[K]V, key string, a ...any) func(_ ...any) map[K]V {
return reload.BuildValFnWithAnyParams(str.Join("widget-config-", key), configFns(m, key, a...))
} }

View File

@ -2,10 +2,10 @@ package widget
import ( import (
"fmt" "fmt"
"github.com/fthvgb1/wp-go/app/cmd/reload"
"github.com/fthvgb1/wp-go/app/pkg/constraints/widgets" "github.com/fthvgb1/wp-go/app/pkg/constraints/widgets"
"github.com/fthvgb1/wp-go/app/theme/wp" "github.com/fthvgb1/wp-go/app/theme/wp"
"github.com/fthvgb1/wp-go/app/wpconfig" "github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/cache/reload"
"github.com/fthvgb1/wp-go/helper/maps" "github.com/fthvgb1/wp-go/helper/maps"
"github.com/fthvgb1/wp-go/helper/slice" "github.com/fthvgb1/wp-go/helper/slice"
str "github.com/fthvgb1/wp-go/helper/strings" str "github.com/fthvgb1/wp-go/helper/strings"
@ -21,34 +21,39 @@ var metaTemplate = `{$before_widget}
{$navCloser} {$navCloser}
{$after_widget}` {$after_widget}`
func metaArgs() map[string]string { func defaultMetaArgs() map[string]string {
return map[string]string{ return map[string]string{
"{$aria_label}": "", "{$aria_label}": "",
"{$title}": "", "{$title}": "",
} }
} }
func Meta(h *wp.Handle, id string) string { var GetMetaArgs = reload.BuildValFnWithAnyParams("widget-meta-args", ParseMetaArgs)
args := reload.GetAnyValBys("widget-meta-args", h, func(h *wp.Handle) map[string]string {
commonArgs := wp.GetComponentsArgs(h, widgets.Widget, map[string]string{})
metaArgs := metaArgs()
args := wp.GetComponentsArgs(h, widgets.Meta, metaArgs)
args = maps.FilterZeroMerge(metaArgs, CommonArgs(), commonArgs, args)
args["{$before_widget}"] = fmt.Sprintf(args["{$before_widget}"], str.Join("meta-", id), str.Join("widget widget_", "meta"))
args["{$title}"] = wpconfig.GetPHPArrayVal("widget_meta", "其它操作", int64(2), "title")
if args["{$title}"] == "" {
args["{$title}"] = "其他操作"
}
if args["{$title}"] != "" {
args["{$h2title}"] = str.Join(args["{$before_title}"], args["{$title}"], args["{$after_title}"])
}
if slice.IsContained(h.CommonThemeMods().ThemeSupport.HTML5, "navigation-widgets") {
args["{$nav}"] = fmt.Sprintf(`<nav aria-label="%s">`, args["{$title}"])
args["{$navCloser}"] = "</nav>"
}
return args
})
func ParseMetaArgs(a ...any) map[string]string {
h := a[0].(*wp.Handle)
id := a[1].(string)
commonArgs := wp.GetComponentsArgs(widgets.Widget, map[string]string{})
metaArgs := defaultMetaArgs()
args := wp.GetComponentsArgs(widgets.Meta, metaArgs)
args = maps.FilterZeroMerge(metaArgs, CommonArgs(), commonArgs, args)
args["{$before_widget}"] = fmt.Sprintf(args["{$before_widget}"], str.Join("meta-", id), str.Join("widget widget_", "meta"))
args["{$title}"] = wpconfig.GetPHPArrayVal("widget_meta", "其它操作", int64(2), "title")
if args["{$title}"] == "" {
args["{$title}"] = "其他操作"
}
if args["{$title}"] != "" {
args["{$h2title}"] = str.Join(args["{$before_title}"], args["{$title}"], args["{$after_title}"])
}
if slice.IsContained(h.CommonThemeMods().ThemeSupport.HTML5, "navigation-widgets") {
args["{$nav}"] = fmt.Sprintf(`<nav aria-label="%s">`, args["{$title}"])
args["{$navCloser}"] = "</nav>"
}
return args
}
func Meta(h *wp.Handle, id string) string {
args := GetMetaArgs(h, id)
ss := str.NewBuilder() ss := str.NewBuilder()
if str.ToInteger(wpconfig.GetOption("users_can_register"), 0) > 0 { if str.ToInteger(wpconfig.GetOption("users_can_register"), 0) > 0 {
ss.Sprintf(`<li><a href="/wp-login.php?action=register">注册</li>`) ss.Sprintf(`<li><a href="/wp-login.php?action=register">注册</li>`)
@ -57,5 +62,5 @@ func Meta(h *wp.Handle, id string) string {
ss.Sprintf(`<li><a href="%s">条目feed</a></li>`, "/feed") ss.Sprintf(`<li><a href="%s">条目feed</a></li>`, "/feed")
ss.Sprintf(`<li><a href="%s">评论feed</a></li>`, "/comments/feed") ss.Sprintf(`<li><a href="%s">评论feed</a></li>`, "/comments/feed")
s := strings.ReplaceAll(metaTemplate, "{$li}", ss.String()) s := strings.ReplaceAll(metaTemplate, "{$li}", ss.String())
return h.ComponentFilterFnHook(widgets.Meta, str.Replace(s, args)) return h.DoActionFilter(widgets.Meta, str.Replace(s, args))
} }

View File

@ -2,11 +2,11 @@ package widget
import ( import (
"fmt" "fmt"
"github.com/fthvgb1/wp-go/app/cmd/reload"
"github.com/fthvgb1/wp-go/app/pkg/cache" "github.com/fthvgb1/wp-go/app/pkg/cache"
"github.com/fthvgb1/wp-go/app/pkg/constraints/widgets" "github.com/fthvgb1/wp-go/app/pkg/constraints/widgets"
"github.com/fthvgb1/wp-go/app/pkg/models" "github.com/fthvgb1/wp-go/app/pkg/models"
"github.com/fthvgb1/wp-go/app/theme/wp" "github.com/fthvgb1/wp-go/app/theme/wp"
"github.com/fthvgb1/wp-go/cache/reload"
"github.com/fthvgb1/wp-go/helper/maps" "github.com/fthvgb1/wp-go/helper/maps"
"github.com/fthvgb1/wp-go/helper/slice" "github.com/fthvgb1/wp-go/helper/slice"
str "github.com/fthvgb1/wp-go/helper/strings" str "github.com/fthvgb1/wp-go/helper/strings"
@ -39,30 +39,37 @@ var recentCommentsTemplate = `{$before_widget}
{$after_widget} {$after_widget}
` `
func RecentComments(h *wp.Handle, id string) string { var GetRecentCommentConf = BuildconfigFn(recentCommentConf, "widget_recent-comments", int64(2))
conf := configs(recentCommentConf, "widget_recent-comments", int64(2))
args := reload.GetAnyValBys("widget-recent-comment-args", h, func(h *wp.Handle) map[string]string { var GetRecentCommentArgs = reload.BuildValFnWithAnyParams("widget-recent-comment-args", RecentCommentArgs)
commentsArgs := recentCommentsArgs()
commonArgs := wp.GetComponentsArgs(h, widgets.Widget, map[string]string{}) func RecentCommentArgs(a ...any) map[string]string {
args := wp.GetComponentsArgs(h, widgets.RecentComments, commentsArgs) h := a[0].(*wp.Handle)
args = maps.FilterZeroMerge(commentsArgs, CommonArgs(), commonArgs, args) conf := a[1].(map[any]any)
args["{$before_widget}"] = fmt.Sprintf(args["{$before_widget}"], str.Join("recent-comments-", id), str.Join("widget widget_", "recent_comments")) id := a[2].(string)
args["{$title}"] = str.Join(args["{$before_title}"], conf["title"].(string), args["{$after_title}"]) commentsArgs := recentCommentsArgs()
if slice.IsContained(h.CommonThemeMods().ThemeSupport.HTML5, "navigation-widgets") { commonArgs := wp.GetComponentsArgs(widgets.Widget, map[string]string{})
args["{$nav}"] = fmt.Sprintf(`<nav aria-label="%s">`, conf["title"]) args := wp.GetComponentsArgs(widgets.RecentComments, commentsArgs)
args["{$navCloser}"] = "</nav>" args = maps.FilterZeroMerge(commentsArgs, CommonArgs(), commonArgs, args)
} args["{$before_widget}"] = fmt.Sprintf(args["{$before_widget}"], str.Join("recent-comments-", id), str.Join("widget widget_", "recent_comments"))
return args args["{$title}"] = str.Join(args["{$before_title}"], conf["title"].(string), args["{$after_title}"])
}) if slice.IsContained(h.CommonThemeMods().ThemeSupport.HTML5, "navigation-widgets") {
args["{$nav}"] = fmt.Sprintf(`<nav aria-label="%s">`, conf["title"])
args["{$navCloser}"] = "</nav>"
}
return args
}
func RecentComments(h *wp.Handle, id string) string {
conf := GetRecentCommentConf()
args := GetRecentCommentArgs(h, conf, id)
comments := slice.Map(cache.RecentComments(h.C, int(conf["number"].(int64))), func(t models.Comments) string { comments := slice.Map(cache.RecentComments(h.C, int(conf["number"].(int64))), func(t models.Comments) string {
return fmt.Sprintf(` <li> return fmt.Sprintf(` <li>
<span class="comment-author-link">%s</span>发表在 <span class="comment-author-link">%s</span>发表在
<a href="/p/%v#comment-%v">%s</a> <a href="%s">%s</a>
</li>`, t.CommentAuthor, t.CommentPostId, t.CommentId, t.PostTitle) </li>`, t.CommentAuthor, t.CommentAuthorUrl, t.PostTitle)
}) })
s := strings.ReplaceAll(recentCommentsTemplate, "{$li}", strings.Join(comments, "\n")) s := strings.ReplaceAll(recentCommentsTemplate, "{$li}", strings.Join(comments, "\n"))
return h.ComponentFilterFnHook(widgets.RecentComments, str.Replace(s, args)) return h.DoActionFilter(widgets.RecentComments, str.Replace(s, args))
} }

View File

@ -2,13 +2,13 @@ package widget
import ( import (
"fmt" "fmt"
"github.com/fthvgb1/wp-go/app/cmd/reload"
"github.com/fthvgb1/wp-go/app/pkg/cache" "github.com/fthvgb1/wp-go/app/pkg/cache"
"github.com/fthvgb1/wp-go/app/pkg/constraints" "github.com/fthvgb1/wp-go/app/pkg/constraints"
"github.com/fthvgb1/wp-go/app/pkg/constraints/widgets" "github.com/fthvgb1/wp-go/app/pkg/constraints/widgets"
"github.com/fthvgb1/wp-go/app/pkg/models" "github.com/fthvgb1/wp-go/app/pkg/models"
"github.com/fthvgb1/wp-go/app/theme/wp" "github.com/fthvgb1/wp-go/app/theme/wp"
"github.com/fthvgb1/wp-go/app/wpconfig" "github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/cache/reload"
"github.com/fthvgb1/wp-go/helper/maps" "github.com/fthvgb1/wp-go/helper/maps"
"github.com/fthvgb1/wp-go/helper/slice" "github.com/fthvgb1/wp-go/helper/slice"
str "github.com/fthvgb1/wp-go/helper/strings" str "github.com/fthvgb1/wp-go/helper/strings"
@ -25,7 +25,7 @@ var recentPostsTemplate = `{$before_widget}
{$after_widget} {$after_widget}
` `
func recentPostsArgs() map[string]string { func DefaultRecentPostsArgs() map[string]string {
return map[string]string{ return map[string]string{
"{$before_sidebar}": "", "{$before_sidebar}": "",
"{$after_sidebar}": "", "{$after_sidebar}": "",
@ -35,7 +35,7 @@ func recentPostsArgs() map[string]string {
} }
} }
func recentConf() map[any]any { func DefaultRecentConf() map[any]any {
return map[any]any{ return map[any]any{
"number": int64(5), "number": int64(5),
"show_date": false, "show_date": false,
@ -43,28 +43,37 @@ func recentConf() map[any]any {
} }
} }
var GetRecentPostConf = reload.BuildValFnWithAnyParams("widget-recent-posts-conf", RecentPostConf)
func RecentPostConf(_ ...any) map[any]any {
recent := DefaultRecentConf()
conf := wpconfig.GetPHPArrayVal[map[any]any]("widget_recent-posts", recent, int64(2))
conf = maps.FilterZeroMerge(recent, conf)
return conf
}
var GetRecentPostArgs = reload.BuildValFnWithAnyParams("widget-recent-posts-args", ParseRecentPostArgs)
func ParseRecentPostArgs(a ...any) map[string]string {
h := a[0].(*wp.Handle)
conf := a[1].(map[any]any)
id := a[2].(string)
recent := DefaultRecentPostsArgs()
commonArgs := wp.GetComponentsArgs(widgets.Widget, map[string]string{})
args := wp.GetComponentsArgs(widgets.RecentPosts, recent)
args = maps.FilterZeroMerge(recent, CommonArgs(), commonArgs, args)
args["{$before_widget}"] = fmt.Sprintf(args["{$before_widget}"], str.Join("recent-posts-", id), str.Join("widget widget_", "recent_entries"))
args["{$title}"] = str.Join(args["{$before_title}"], conf["title"].(string), args["{$after_title}"])
if slice.IsContained(h.CommonThemeMods().ThemeSupport.HTML5, "navigation-widgets") {
args["{$nav}"] = fmt.Sprintf(`<nav aria-label="%s">`, conf["title"])
args["{$navCloser}"] = "</nav>"
}
return args
}
func RecentPosts(h *wp.Handle, id string) string { func RecentPosts(h *wp.Handle, id string) string {
conf := reload.GetAnyValBys("widget-recent-posts-conf", h, func(h *wp.Handle) map[any]any { conf := GetRecentPostConf()
recent := recentConf() args := GetRecentPostArgs(h, conf, id)
conf := wpconfig.GetPHPArrayVal[map[any]any]("widget_recent-posts", recent, int64(2))
conf = maps.FilterZeroMerge(recent, conf)
return conf
})
args := reload.GetAnyValBys("widget-recent-posts-args", h, func(h *wp.Handle) map[string]string {
recent := recentPostsArgs()
commonArgs := wp.GetComponentsArgs(h, widgets.Widget, map[string]string{})
args := wp.GetComponentsArgs(h, widgets.RecentPosts, recent)
args = maps.FilterZeroMerge(recent, CommonArgs(), commonArgs, args)
args["{$before_widget}"] = fmt.Sprintf(args["{$before_widget}"], str.Join("recent-posts-", id), str.Join("widget widget_", "recent_entries"))
args["{$title}"] = str.Join(args["{$before_title}"], conf["title"].(string), args["{$after_title}"])
if slice.IsContained(h.CommonThemeMods().ThemeSupport.HTML5, "navigation-widgets") {
args["{$nav}"] = fmt.Sprintf(`<nav aria-label="%s">`, conf["title"])
args["{$navCloser}"] = "</nav>"
}
return args
})
currentPostId := uint64(0) currentPostId := uint64(0)
if h.Scene() == constraints.Detail { if h.Scene() == constraints.Detail {
currentPostId = str.ToInteger(h.C.Param("id"), uint64(0)) currentPostId = str.ToInteger(h.C.Param("id"), uint64(0))
@ -85,5 +94,5 @@ func RecentPosts(h *wp.Handle, id string) string {
</li>`, t.Id, ariaCurrent, t.PostTitle, date) </li>`, t.Id, ariaCurrent, t.PostTitle, date)
}) })
s := strings.ReplaceAll(recentPostsTemplate, "{$li}", strings.Join(posts, "\n")) s := strings.ReplaceAll(recentPostsTemplate, "{$li}", strings.Join(posts, "\n"))
return h.ComponentFilterFnHook(widgets.RecentPosts, str.Replace(s, args)) return h.DoActionFilter(widgets.RecentPosts, str.Replace(s, args))
} }

View File

@ -2,11 +2,11 @@ package widget
import ( import (
"fmt" "fmt"
"github.com/fthvgb1/wp-go/app/cmd/reload"
"github.com/fthvgb1/wp-go/app/pkg/constraints" "github.com/fthvgb1/wp-go/app/pkg/constraints"
"github.com/fthvgb1/wp-go/app/pkg/constraints/widgets" "github.com/fthvgb1/wp-go/app/pkg/constraints/widgets"
"github.com/fthvgb1/wp-go/app/theme/wp" "github.com/fthvgb1/wp-go/app/theme/wp"
"github.com/fthvgb1/wp-go/app/wpconfig" "github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/cache/reload"
"github.com/fthvgb1/wp-go/helper/html" "github.com/fthvgb1/wp-go/helper/html"
"github.com/fthvgb1/wp-go/helper/maps" "github.com/fthvgb1/wp-go/helper/maps"
"github.com/fthvgb1/wp-go/helper/slice" "github.com/fthvgb1/wp-go/helper/slice"
@ -47,35 +47,41 @@ func searchArgs() map[string]string {
var form = html5SearchForm var form = html5SearchForm
var GetSearchArgs = reload.BuildValFnWithAnyParams("widget-search-args", ParseSearchArgs)
func ParseSearchArgs(a ...any) map[string]string {
h := a[0].(*wp.Handle)
id := a[1].(string)
search := searchArgs()
commonArgs := wp.GetComponentsArgs(widgets.Widget, map[string]string{})
args := wp.GetComponentsArgs(widgets.Search, search)
args = maps.FilterZeroMerge(search, CommonArgs(), commonArgs, args)
args["{$before_widget}"] = fmt.Sprintf(args["{$before_widget}"], str.Join("search-", id), str.Join("widget widget_", "search"))
if args["{$title}"] == "" {
args["{$title}"] = wpconfig.GetPHPArrayVal("widget_search", "", int64(2), "title")
}
if args["{$title}"] != "" {
args["{$title}"] = str.Join(args["{$before_title}"], args["{$title}"], args["{$after_title}"])
}
if args["{$form}"] != "" {
form = args["{$form}"]
delete(args, "{$form}")
}
if !slice.IsContained(h.CommonThemeMods().ThemeSupport.HTML5, "navigation-widgets") {
form = xmlSearchForm
}
return args
}
func Search(h *wp.Handle, id string) string { func Search(h *wp.Handle, id string) string {
args := reload.GetAnyValBys("widget-search-args", h, func(h *wp.Handle) map[string]string { args := GetSearchArgs(h, id)
search := searchArgs()
commonArgs := wp.GetComponentsArgs(h, widgets.Widget, map[string]string{})
args := wp.GetComponentsArgs(h, widgets.Search, search)
args = maps.FilterZeroMerge(search, CommonArgs(), commonArgs, args)
args["{$before_widget}"] = fmt.Sprintf(args["{$before_widget}"], str.Join("search-", id), str.Join("widget widget_", "search"))
if args["{$title}"] == "" {
args["{$title}"] = wpconfig.GetPHPArrayVal("widget_search", "", int64(2), "title")
}
if args["{$title}"] != "" {
args["{$title}"] = str.Join(args["{$before_title}"], args["{$title}"], args["{$after_title}"])
}
if args["{$form}"] != "" {
form = args["{$form}"]
delete(args, "{$form}")
}
if !slice.IsContained(h.CommonThemeMods().ThemeSupport.HTML5, "navigation-widgets") {
form = xmlSearchForm
}
return args
})
s := strings.ReplaceAll(searchTemplate, "{$form}", form) s := strings.ReplaceAll(searchTemplate, "{$form}", form)
val := "" val := ""
if h.Scene() == constraints.Search { if h.Scene() == constraints.Search {
val = html.SpecialChars(h.Index.Param.Search) val = html.SpecialChars(h.GetIndexHandle().Param.Search)
} }
s = strings.ReplaceAll(s, "{$value}", val) s = strings.ReplaceAll(s, "{$value}", val)
return h.ComponentFilterFnHook(widgets.Search, str.Replace(s, args)) return h.DoActionFilter(widgets.Search, str.Replace(s, args))
} }

View File

@ -3,32 +3,38 @@ package wp
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/fthvgb1/wp-go/app/cmd/reload"
"github.com/fthvgb1/wp-go/app/pkg/cache" "github.com/fthvgb1/wp-go/app/pkg/cache"
"github.com/fthvgb1/wp-go/app/pkg/logs" "github.com/fthvgb1/wp-go/app/pkg/logs"
"github.com/fthvgb1/wp-go/app/pkg/models" "github.com/fthvgb1/wp-go/app/pkg/models"
"github.com/fthvgb1/wp-go/app/wpconfig" "github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/cache/reload"
"github.com/fthvgb1/wp-go/helper/slice" "github.com/fthvgb1/wp-go/helper/slice"
str "github.com/fthvgb1/wp-go/helper/strings" str "github.com/fthvgb1/wp-go/helper/strings"
"github.com/fthvgb1/wp-go/model" "github.com/fthvgb1/wp-go/model"
"regexp" "regexp"
"strings"
) )
func (h *Handle) DisplayHeaderText() bool { func (h *Handle) DisplayHeaderText() bool {
return h.themeMods.ThemeSupport.CustomHeader.HeaderText && "blank" != h.themeMods.HeaderTextcolor return h.themeMods.ThemeSupport.CustomHeader.HeaderText && "blank" != h.themeMods.HeaderTextcolor
} }
var GetCustomHeaderImgFn = reload.BuildValFnWithConfirm("headerImages", customHeadImag, 5)
func customHeadImag(h *Handle) ([]models.PostThumbnail, bool) {
hs, err := h.GetHeaderImages(h.theme)
if err != nil {
h.SetErr(fmt.Errorf("get customheadimage err: %v", err), Low)
return nil, false
}
return hs, true
}
func (h *Handle) GetCustomHeaderImg() (r models.PostThumbnail, isRand bool) { func (h *Handle) GetCustomHeaderImg() (r models.PostThumbnail, isRand bool) {
var err error var err error
img := reload.GetAnyValBys("headerImages", h.theme, func(theme string) []models.PostThumbnail { img := GetCustomHeaderImgFn(h)
hs, er := h.GetHeaderImages(h.theme) err = h.Err()
if er != nil { if err != nil && strings.Contains(err.Error(), "get customheadimage err") {
err = er
return nil
}
return hs
})
if err != nil {
logs.Error(err, "获取页眉背景图失败") logs.Error(err, "获取页眉背景图失败")
return return
} }
@ -85,7 +91,7 @@ func GetVideoSetting(h *Handle, u string) (string, error) {
if is := videoReg.FindString(u); is != "" { if is := videoReg.FindString(u); is != "" {
v.MimeType = "video/x-youtube" v.MimeType = "video/x-youtube"
} }
_ = h.ComponentFilterFnHook("videoSetting", "", &v) _ = h.DoActionFilter("videoSetting", "", &v)
s, err := json.Marshal(v) s, err := json.Marshal(v)
if err != nil { if err != nil {
return "", err return "", err
@ -134,13 +140,14 @@ func CustomVideo(h *Handle, scene ...string) (ok bool) {
} }
scripts = slice.Map(scripts, func(t string) string { scripts = slice.Map(scripts, func(t string) string {
return fmt.Sprintf(`<script src="%s" id="wp-%s-js"></script> return fmt.Sprintf(`<script src="%s" id="wp-%s-js"></script>
`, t, str.Replaces(t, [][]string{ `, t, str.Replaces(t, []string{
{"/wp-includes/js/dist/vendor/"}, "/wp-includes/js/dist/vendor/",
{"/wp-includes/js/dist/"}, "/wp-includes/js/dist/",
{"/wp-includes/js/"}, "/wp-includes/js/",
{".min.js"}, ".min.js",
{".js"}, ".js",
{"wp-", ""}, "wp-",
"",
})) }))
}) })

View File

@ -2,9 +2,9 @@ package wp
import ( import (
"fmt" "fmt"
"github.com/fthvgb1/wp-go/app/cmd/reload"
"github.com/fthvgb1/wp-go/app/pkg/cache" "github.com/fthvgb1/wp-go/app/pkg/cache"
"github.com/fthvgb1/wp-go/app/wpconfig" "github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/cache/reload"
"github.com/fthvgb1/wp-go/helper/maps" "github.com/fthvgb1/wp-go/helper/maps"
str "github.com/fthvgb1/wp-go/helper/strings" str "github.com/fthvgb1/wp-go/helper/strings"
) )
@ -42,10 +42,10 @@ func CalCustomLogo(h *Handle) (r string) {
return return
} }
var GetCustomLog = reload.BuildValFn("customLogo", CalCustomLogo)
func customLogo(h *Handle) func() string { func customLogo(h *Handle) func() string {
return func() string { return func() string {
return reload.GetAnyValBys("customLogo", h, func(h *Handle) string { return GetCustomLog(h)
return CalCustomLogo(h)
})
} }
} }

View File

@ -10,18 +10,34 @@ import (
"github.com/fthvgb1/wp-go/app/plugins" "github.com/fthvgb1/wp-go/app/plugins"
"github.com/fthvgb1/wp-go/app/plugins/wpposts" "github.com/fthvgb1/wp-go/app/plugins/wpposts"
"github.com/fthvgb1/wp-go/app/wpconfig" "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" str "github.com/fthvgb1/wp-go/helper/strings"
"github.com/fthvgb1/wp-go/plugin/pagination"
"strings"
"time"
) )
type DetailHandle struct { type DetailHandle struct {
*Handle *Handle
CommentRender plugins.CommentHtml CommentRender plugins.CommentHtml
Comments []models.Comments Comments []uint64
Post models.Posts Page int
Limit int
Post models.Posts
CommentPageEle pagination.Render
TotalRaw int
TotalPage int
CommentStep int
} }
func NewDetailHandle(handle *Handle) *DetailHandle { func NewDetailHandle(handle *Handle) *DetailHandle {
return &DetailHandle{Handle: handle} return &DetailHandle{
Handle: handle,
Page: 1,
Limit: 5,
CommentStep: 1,
}
} }
func (d *DetailHandle) BuildDetailData() (err error) { func (d *DetailHandle) BuildDetailData() (err error) {
@ -30,7 +46,7 @@ func (d *DetailHandle) BuildDetailData() (err error) {
if err != nil { if err != nil {
return return
} }
d.Comment() d.CommentData()
d.ContextPost() d.ContextPost()
return return
} }
@ -68,12 +84,66 @@ func (d *DetailHandle) PasswordProject() {
} }
} }
} }
func (d *DetailHandle) Comment() { func (h *Handle) GetDetailHandle() *DetailHandle {
comments, err := cache.PostComments(d.C, d.Post.Id) v, ok := h.C.Get("detailHandle")
logs.IfError(err, "get d.Post comment", d.Post.Id) if !ok {
d.ginH["comments"] = comments vv := NewDetailHandle(h)
d.Comments = comments h.C.Set("detailHandle", vv)
return vv
}
return v.(*DetailHandle)
}
func (d *DetailHandle) CommentData() {
d.ginH["totalCommentNum"] = 0
d.ginH["totalCommentPage"] = 1
d.ginH["commentPageNav"] = ""
order := wpconfig.GetOption("comment_order")
d.ginH["commentOrder"] = order
d.Limit = str.ToInteger(wpconfig.GetOption("comments_per_page"), 5)
pageComments := wpconfig.GetOption("page_comments")
num, err := cachemanager.GetBy[int]("commentNumber", d.C, d.Post.Id, time.Second)
if err != nil {
d.SetErr(err, Low)
return
}
if num < 1 {
return
}
topNum, err := cachemanager.GetBy[int]("postTopCommentsNum", d.C, d.Post.Id, time.Second)
if err != nil {
d.SetErr(err, Low)
return
}
d.TotalPage = number.DivideCeil(topNum, d.Limit)
if !strings.Contains(d.C.Request.URL.Path, "comment-page") {
defaultCommentsPage := wpconfig.GetOption("default_comments_page")
if order == "desc" && defaultCommentsPage == "oldest" || order == "asc" && defaultCommentsPage == "newest" {
d.C.AddParam("page", number.IntToString(d.TotalPage))
}
}
d.Page = str.ToInteger(d.C.Param("page"), 1)
d.ginH["currentPage"] = d.Page
var key string
if pageComments != "1" {
key = number.IntToString(d.Post.Id)
d.Limit = 0
} else {
key = fmt.Sprintf("%d-%d-%d", d.Post.Id, d.Page, d.Limit)
}
d.ginH["page_comments"] = pageComments
d.ginH["totalCommentPage"] = d.TotalPage
if d.TotalPage < d.Page {
d.SetErr(errors.New("curren page above total page"), High)
return
}
data, err := cache.PostTopLevelCommentIds(d.C, d.Post.Id, d.Page, d.Limit, topNum, order, key)
if err != nil {
d.SetErr(err, Low)
return
}
d.TotalRaw = topNum
d.ginH["totalCommentNum"] = num
d.Comments = data
} }
func (d *DetailHandle) RenderComment() { func (d *DetailHandle) RenderComment() {
@ -86,9 +156,21 @@ func (d *DetailHandle) RenderComment() {
ableComment = false ableComment = false
} }
d.ginH["showComment"] = ableComment d.ginH["showComment"] = ableComment
if len(d.Comments) > 0 && ableComment { d.ginH["comments"] = ""
dep := str.ToInteger(wpconfig.GetOption("thread_comments_depth"), 5) if len(d.Comments) < 0 || !ableComment {
d.ginH["comments"] = plugins.FormatComments(d.C, d.CommentRender, d.Comments, dep) 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, High)
return
}
if d.CommentPageEle == nil {
d.CommentPageEle = plugins.TwentyFifteenCommentPagination()
}
if wpconfig.GetOption("page_comments") == "1" && d.TotalPage > 1 {
d.ginH["commentPageNav"] = pagination.Paginate(d.CommentPageEle, d.TotalRaw, d.Limit, d.Page, d.CommentStep, *d.C.Request.URL, d.IsHttps())
} }
} }
@ -103,16 +185,17 @@ func DetailRender(h *Handle) {
if h.Stats != constraints.Ok { if h.Stats != constraints.Ok {
return return
} }
d := h.Detail d := h.GetDetailHandle()
d.PasswordProject() d.PasswordProject()
d.RenderComment() d.RenderComment()
d.ginH["post"] = d.Post d.ginH["post"] = d.Post
} }
func Detail(h *Handle) { func Detail(h *Handle) {
err := h.Detail.BuildDetailData() d := h.GetDetailHandle()
err := d.BuildDetailData()
if err != nil { if err != nil {
h.Detail.SetErr(err) d.SetErr(err, High)
} }
h.SetData("scene", h.Scene()) h.SetData("scene", h.Scene())
} }
@ -120,7 +203,7 @@ func Detail(h *Handle) {
func ReplyCommentJs(h *Handle) { func ReplyCommentJs(h *Handle) {
h.PushFooterScript(constraints.Detail, NewComponent("comment-reply.js", "", false, 10, func(h *Handle) string { h.PushFooterScript(constraints.Detail, NewComponent("comment-reply.js", "", false, 10, func(h *Handle) string {
reply := "" reply := ""
if h.Detail.Post.CommentStatus == "open" && wpconfig.GetOption("thread_comments") == "1" { if h.GetDetailHandle().Post.CommentStatus == "open" && wpconfig.GetOption("thread_comments") == "1" {
reply = `<script src='/wp-includes/js/comment-reply.min.js' id='comment-reply-js'></script>` reply = `<script src='/wp-includes/js/comment-reply.min.js' id='comment-reply-js'></script>`
} }
return reply return reply

View File

@ -0,0 +1,8 @@
package wp
const (
None = iota
Low
High
Fatal
)

View File

@ -2,13 +2,14 @@ package wp
import ( import (
"errors" "errors"
"github.com/fthvgb1/wp-go/safety"
) )
var fnMap map[string]map[string]any var fnMap = safety.NewMap[string, map[string]any]()
var fnHook map[string]map[string]any var fnHook = safety.NewMap[string, map[string]any]()
func GetFn[T any](fnType string, name string) []T { func GetFn[T any](fnType string, name string) []T {
v, ok := fnMap[fnType] v, ok := fnMap.Load(fnType)
if !ok { if !ok {
return nil return nil
} }
@ -19,7 +20,7 @@ func GetFn[T any](fnType string, name string) []T {
return vv.([]T) return vv.([]T)
} }
func GetFnHook[T any](fnType string, name string) []T { func GetFnHook[T any](fnType string, name string) []T {
v, ok := fnHook[fnType] v, ok := fnHook.Load(fnType)
if !ok { if !ok {
return nil return nil
} }
@ -31,10 +32,10 @@ func GetFnHook[T any](fnType string, name string) []T {
} }
func PushFn[T any](fnType string, name string, fns ...T) error { func PushFn[T any](fnType string, name string, fns ...T) error {
v, ok := fnMap[fnType] v, ok := fnMap.Load(fnType)
if !ok { if !ok {
v = make(map[string]any) v = make(map[string]any)
fnMap[fnType] = v fnMap.Store(fnType, v)
v[name] = fns v[name] = fns
return nil return nil
} }
@ -52,10 +53,10 @@ func PushFn[T any](fnType string, name string, fns ...T) error {
} }
func PushFnHook[T any](fnType string, name string, fns ...T) error { func PushFnHook[T any](fnType string, name string, fns ...T) error {
v, ok := fnHook[fnType] v, ok := fnHook.Load(fnType)
if !ok { if !ok {
v = make(map[string]any) v = make(map[string]any)
fnHook[fnType] = v fnHook.Store(fnType, v)
v[name] = fns v[name] = fns
return nil return nil
} }

View File

@ -2,12 +2,13 @@ package wp
import ( import (
"database/sql" "database/sql"
"errors"
"fmt" "fmt"
"github.com/fthvgb1/wp-go/app/cmd/reload"
"github.com/fthvgb1/wp-go/app/pkg/cache" "github.com/fthvgb1/wp-go/app/pkg/cache"
"github.com/fthvgb1/wp-go/app/pkg/constraints" "github.com/fthvgb1/wp-go/app/pkg/constraints"
"github.com/fthvgb1/wp-go/app/pkg/models" "github.com/fthvgb1/wp-go/app/pkg/models"
"github.com/fthvgb1/wp-go/app/plugins" "github.com/fthvgb1/wp-go/app/plugins"
"github.com/fthvgb1/wp-go/cache/reload"
"github.com/fthvgb1/wp-go/helper/number" "github.com/fthvgb1/wp-go/helper/number"
"github.com/fthvgb1/wp-go/helper/slice" "github.com/fthvgb1/wp-go/helper/slice"
"github.com/fthvgb1/wp-go/model" "github.com/fthvgb1/wp-go/model"
@ -19,11 +20,21 @@ type IndexHandle struct {
*Handle *Handle
Param *IndexParams Param *IndexParams
Posts []models.Posts Posts []models.Posts
pageEle pagination.Elements pageEle pagination.Render
TotalRows int TotalRows int
postsPlugin PostsPlugin postsPlugin PostsPlugin
} }
func (h *Handle) GetIndexHandle() *IndexHandle {
v, ok := h.C.Get("indexHandle")
if !ok {
vv := NewIndexHandle(h)
h.C.Set("indexHandle", vv)
return vv
}
return v.(*IndexHandle)
}
func (i *IndexHandle) ListPlugin() func(*Handle, *models.Posts) { func (i *IndexHandle) ListPlugin() func(*Handle, *models.Posts) {
return i.postsPlugin return i.postsPlugin
} }
@ -32,11 +43,11 @@ func (i *IndexHandle) SetListPlugin(listPlugin func(*Handle, *models.Posts)) {
i.postsPlugin = listPlugin i.postsPlugin = listPlugin
} }
func (i *IndexHandle) PageEle() pagination.Elements { func (i *IndexHandle) PageEle() pagination.Render {
return i.pageEle return i.pageEle
} }
func (i *IndexHandle) SetPageEle(pageEle pagination.Elements) { func (i *IndexHandle) SetPageEle(pageEle pagination.Render) {
i.pageEle = pageEle i.pageEle = pageEle
} }
@ -88,14 +99,14 @@ func (i *IndexHandle) GetIndexData() (posts []models.Posts, totalRaw int, err er
switch i.scene { switch i.scene {
case constraints.Home, constraints.Category, constraints.Tag, constraints.Author: case constraints.Home, constraints.Category, constraints.Tag, constraints.Author:
posts, totalRaw, err = cache.PostLists(i.C, i.Param.CacheKey, i.C, q, i.Param.Page, i.Param.PageSize) posts, totalRaw, err = cache.PostLists(i.C, i.Param.CacheKey, q, i.Param.Page, i.Param.PageSize)
if i.scene == constraints.Home && i.Param.Page == 1 { if i.scene == constraints.Home && i.Param.Page == 1 {
i.MarkSticky(&posts) i.MarkSticky(&posts)
} }
case constraints.Search: case constraints.Search:
posts, totalRaw, err = cache.SearchPost(i.C, i.Param.CacheKey, i.C, q, i.Param.Page, i.Param.PageSize) posts, totalRaw, err = cache.SearchPost(i.C, i.Param.CacheKey, q, i.Param.Page, i.Param.PageSize)
case constraints.Archive: case constraints.Archive:
i.ginH["archiveYear"] = i.Param.Year i.ginH["archiveYear"] = i.Param.Year
@ -115,34 +126,36 @@ func (i *IndexHandle) Pagination() {
if q != "" { if q != "" {
q = fmt.Sprintf("?%s", q) q = fmt.Sprintf("?%s", q)
} }
paginations := pagination.NewParsePagination(i.TotalRows, i.Param.PageSize, i.Param.Page, i.Param.PaginationStep, q, i.C.Request.URL.Path) i.ginH["pagination"] = pagination.Paginate(i.pageEle, i.TotalRows, i.Param.PageSize, i.Param.Page, i.Param.PaginationStep, *i.C.Request.URL, i.IsHttps())
i.ginH["pagination"] = pagination.Paginate(i.pageEle, paginations)
} }
func (i *IndexHandle) BuildIndexData(parm *IndexParams) (err error) { func (i *IndexHandle) BuildIndexData() (err error) {
err = i.ParseIndex(parm) if i.Param == nil {
i.Param = NewIndexParams(i.C)
}
err = i.ParseIndex(i.Param)
if err != nil { if err != nil {
i.Stats = constraints.ParamError i.Stats = constraints.ParamError
return return
} }
posts, totalRows, err := i.GetIndexData() posts, totalRows, err := i.GetIndexData()
if err != nil && err != sql.ErrNoRows { if err != nil && !errors.Is(err, sql.ErrNoRows) {
i.Stats = constraints.Error404 i.Stats = constraints.Error404
return return
} }
i.Posts = posts i.Posts = posts
i.TotalRows = totalRows i.TotalRows = totalRows
i.ginH["totalPage"] = number.CalTotalPage(totalRows, i.Param.PageSize) i.ginH["totalPage"] = number.DivideCeil(totalRows, i.Param.PageSize)
return return
} }
var GetPostsPlugin = reload.BuildValFnWithAnyParams("postPlugins", UsePostsPlugins)
func (i *IndexHandle) ExecPostsPlugin() { func (i *IndexHandle) ExecPostsPlugin() {
fn := i.postsPlugin fn := i.postsPlugin
if fn == nil { if fn == nil {
fn = reload.GetAnyValBys("postPlugins", i, func(a *IndexHandle) PostsPlugin { fn = GetPostsPlugin()
return UsePostsPlugins()
})
} }
for j := range i.Posts { for j := range i.Posts {
fn(i.Handle, &i.Posts[j]) fn(i.Handle, &i.Posts[j])
@ -150,27 +163,27 @@ func (i *IndexHandle) ExecPostsPlugin() {
} }
func IndexRender(h *Handle) { func IndexRender(h *Handle) {
i := h.Index i := h.GetIndexHandle()
i.ExecPostsPlugin() i.ExecPostsPlugin()
i.Pagination() i.Pagination()
i.ginH["posts"] = i.Posts i.ginH["posts"] = i.Posts
} }
func Index(h *Handle) { func Index(h *Handle) {
i := h.Index i := h.GetIndexHandle()
err := i.BuildIndexData(NewIndexParams(i.C)) err := i.BuildIndexData()
if err != nil { if err != nil {
i.SetErr(err) i.SetErr(err, High)
} }
h.SetData("scene", h.Scene()) h.SetData("scene", h.Scene())
} }
func (i *IndexHandle) MarkSticky(posts *[]models.Posts) { func (i *IndexHandle) MarkSticky(posts *[]models.Posts) {
a := i.StickPosts() a := GetStickPosts(i.Handle)
if len(a) < 1 { if len(a) < 1 {
return return
} }
m := i.StickMapPosts() m := GetStickMapPosts(i.Handle)
*posts = append(a, slice.Filter(*posts, func(post models.Posts, _ int) bool { *posts = append(a, slice.Filter(*posts, func(post models.Posts, _ int) bool {
_, ok := m[post.Id] _, ok := m[post.Id]
return !ok return !ok

View File

@ -105,18 +105,16 @@ func NewIndexParams(ctx *gin.Context) *IndexParams {
func (i *IndexParams) ParseSearchs() { func (i *IndexParams) ParseSearchs() {
s := i.Ctx.Query("s") s := i.Ctx.Query("s")
if s != "" { q := str.Join("%", s, "%")
q := str.Join("%", s, "%") i.Where = append(i.Where, []string{
i.Where = append(i.Where, []string{ "and", "post_title", "like", q, "",
"and", "post_title", "like", q, "", "or", "post_content", "like", q, "",
"or", "post_content", "like", q, "", "or", "post_excerpt", "like", q, "",
"or", "post_excerpt", "like", q, "", }, []string{"post_password", ""})
}, []string{"post_password", ""}) i.PostType = append(i.PostType, "Page", "attachment")
i.PostType = append(i.PostType, "Page", "attachment") i.Header = fmt.Sprintf("<span>%s</span>的搜索结果", s)
i.Header = fmt.Sprintf("<span>%s</span>的搜索结果", s) i.setTitleLR(str.Join(`"`, s, `"`, "的搜索结果"), i.BlogName)
i.setTitleLR(str.Join(`"`, s, `"`, "的搜索结果"), i.BlogName) i.Search = s
i.Search = s
}
} }
func (i *IndexParams) ParseArchives() error { func (i *IndexParams) ParseArchives() error {
year := i.Ctx.Param("year") year := i.Ctx.Param("year")

View File

@ -1,11 +1,11 @@
package wp package wp
import ( import (
"github.com/fthvgb1/wp-go/app/cmd/reload"
"github.com/fthvgb1/wp-go/app/pkg/config" "github.com/fthvgb1/wp-go/app/pkg/config"
"github.com/fthvgb1/wp-go/app/pkg/models" "github.com/fthvgb1/wp-go/app/pkg/models"
"github.com/fthvgb1/wp-go/app/plugins" "github.com/fthvgb1/wp-go/app/plugins"
"github.com/fthvgb1/wp-go/app/plugins/wpposts" "github.com/fthvgb1/wp-go/app/plugins/wpposts"
"github.com/fthvgb1/wp-go/cache/reload"
"github.com/fthvgb1/wp-go/helper/maps" "github.com/fthvgb1/wp-go/helper/maps"
"github.com/fthvgb1/wp-go/helper/slice" "github.com/fthvgb1/wp-go/helper/slice"
) )
@ -23,7 +23,7 @@ func PostsPlugins(initial PostsPlugin, calls ...func(PostsPlugin, *Handle, *mode
var pluginFns = reload.Vars(map[string]func(PostsPlugin, *Handle, *models.Posts){ var pluginFns = reload.Vars(map[string]func(PostsPlugin, *Handle, *models.Posts){
"passwordProject": PasswordProject, "passwordProject": PasswordProject,
"digest": Digest, "digest": Digest,
}) }, "list-post-plugins-fns")
func (h *Handle) PushPostsPlugin(name string, fn func(PostsPlugin, *Handle, *models.Posts)) { func (h *Handle) PushPostsPlugin(name string, fn func(PostsPlugin, *Handle, *models.Posts)) {
m := pluginFns.Load() m := pluginFns.Load()
@ -53,7 +53,7 @@ func Digest(next PostsPlugin, h *Handle, post *models.Posts) {
next(h, post) next(h, post)
} }
var ordinaryPlugin = reload.Vars([]PostsPlugin{}) var ordinaryPlugin = reload.Vars([]PostsPlugin{}, "ordinaryPlugin")
func (h *Handle) PushPostPlugin(plugin ...PostsPlugin) { func (h *Handle) PushPostPlugin(plugin ...PostsPlugin) {
p := ordinaryPlugin.Load() p := ordinaryPlugin.Load()
@ -69,7 +69,7 @@ func PostPlugin(calls ...PostsPlugin) PostsPlugin {
} }
} }
func UsePostsPlugins() PostsPlugin { func UsePostsPlugins(_ ...any) PostsPlugin {
m := pluginFns.Load() m := pluginFns.Load()
pluginss := slice.FilterAndMap(config.GetConfig().ListPagePlugins, func(t string) (func(PostsPlugin, *Handle, *models.Posts), bool) { pluginss := slice.FilterAndMap(config.GetConfig().ListPagePlugins, func(t string) (func(PostsPlugin, *Handle, *models.Posts), bool) {
f, ok := m[t] f, ok := m[t]

View File

@ -0,0 +1,167 @@
package middleware
import (
"github.com/fthvgb1/wp-go/app/pkg/cache"
"github.com/fthvgb1/wp-go/app/pkg/constraints"
"github.com/fthvgb1/wp-go/app/theme/wp"
"github.com/fthvgb1/wp-go/app/theme/wp/components/widget"
"github.com/fthvgb1/wp-go/app/theme/wp/route"
"github.com/fthvgb1/wp-go/cache/reload"
"github.com/fthvgb1/wp-go/helper/maps"
"github.com/fthvgb1/wp-go/helper/number"
str "github.com/fthvgb1/wp-go/helper/strings"
"net/http"
"strconv"
"time"
)
var plainRouteParam = reload.Vars([]Plain{
{
Action: "p",
Param: map[string]string{
"p": "id",
"cpage": "page",
},
Scene: constraints.Detail,
},
{
Action: "s",
Scene: constraints.Search,
},
{
Scene: constraints.Category,
Fn: func(h *wp.Handle) bool {
c, ok := widget.IsCategory(h)
if !ok {
return false
}
h.C.AddParam("category", c.Name)
h.C.AddParam("page", h.C.Query("paged"))
return true
},
},
{
Scene: constraints.Tag,
Action: "tag",
Param: map[string]string{
"tag": "tag",
"paged": "page",
},
},
{
Scene: constraints.Archive,
Fn: func(h *wp.Handle) bool {
m := h.C.Query("m")
if m == "" {
return false
}
t, err := time.Parse("200601", m)
if err != nil {
return false
}
h.C.AddParam("year", strconv.Itoa(t.Year()))
h.C.AddParam("month", number.IntToString(t.Month()))
h.C.AddParam("page", h.C.Query("paged"))
return true
},
},
{
Scene: constraints.Author,
Fn: func(h *wp.Handle) bool {
u := h.C.Query("author")
if u == "" {
return false
}
users := GetUsersIds(h)
name, ok := users[str.ToInteger[uint64](u, 0)]
if !ok {
return false
}
h.C.AddParam("author", name)
h.C.AddParam("page", h.C.Query("paged"))
return true
},
},
}, "plainRouteParam")
var GetUsersIds = reload.BuildValFnWithConfirm("usersIds", func(h *wp.Handle) (map[uint64]string, bool) {
users, err := cache.GetAllUsername(h.C)
if err != nil {
return nil, false
}
return maps.Flip(users), true
}, 10)
func SetExplainRouteParam(p []Plain) {
plainRouteParam.Store(p)
}
func GetExplainRouteParam() []Plain {
return plainRouteParam.Load()
}
func PushExplainRouteParam(explain ...Plain) {
v := plainRouteParam.Load()
v = append(v, explain...)
plainRouteParam.Store(v)
}
type Plain struct {
Action string
Param map[string]string
Scene string
Fn func(h *wp.Handle) bool
}
func MixWithPlain(h *wp.Handle) {
for _, explain := range plainRouteParam.Load() {
if explain.Action == "" && explain.Fn == nil {
continue
}
if explain.Fn != nil {
if !explain.Fn(h) {
continue
}
if explain.Scene != "" {
h.SetScene(explain.Scene)
}
wp.Run(h, nil)
h.Abort()
return
}
if explain.Scene == "" {
continue
}
q := h.C.Query(explain.Action)
if q == "" {
continue
}
h.SetScene(explain.Scene)
for query, param := range explain.Param {
h.C.AddParam(param, h.C.Query(query))
}
wp.Run(h, nil)
h.Abort()
return
}
}
func ShowPreComment(h *wp.Handle) {
v, ok := cache.NewCommentCache().Get(h.C, h.C.Request.URL.RawQuery)
if ok {
h.C.Writer.Header().Set("Content-Type", "text/html; charset=utf-8")
h.C.Writer.WriteHeader(http.StatusOK)
_, _ = h.C.Writer.Write([]byte(v))
h.Abort()
}
}
func CommonMiddleware(h *wp.Handle) {
h.PushHandler(constraints.PipeMiddleware, constraints.Home,
wp.NewHandleFn(MixWithPlain, 100, "middleware.MixWithPlain"),
)
h.PushHandler(constraints.PipeMiddleware, constraints.Detail,
wp.NewHandleFn(ShowPreComment, 100, "middleware.ShowPreComment"),
)
h.PushHandler(constraints.PipeMiddleware, constraints.NoRoute,
wp.NewHandleFn(route.ResolveRoute, 100, "route.ResolveRoute"),
)
}

View File

@ -1,9 +1,8 @@
package wp package wp
import ( import (
"github.com/fthvgb1/wp-go/app/cmd/reload"
"github.com/fthvgb1/wp-go/app/pkg/constraints" "github.com/fthvgb1/wp-go/app/pkg/constraints"
"github.com/fthvgb1/wp-go/helper" "github.com/fthvgb1/wp-go/cache/reload"
"github.com/fthvgb1/wp-go/helper/slice" "github.com/fthvgb1/wp-go/helper/slice"
str "github.com/fthvgb1/wp-go/helper/strings" str "github.com/fthvgb1/wp-go/helper/strings"
) )
@ -22,7 +21,7 @@ func NewPipe(name string, order float64, fn HandlePipeFn[*Handle]) Pipe {
// HandlePipe 方便把功能写在其它包里 // HandlePipe 方便把功能写在其它包里
func HandlePipe[T any](initial func(T), fns ...HandlePipeFn[T]) HandleFn[T] { func HandlePipe[T any](initial func(T), fns ...HandlePipeFn[T]) HandleFn[T] {
return slice.ReverseReduce(fns, func(next HandlePipeFn[T], f func(t T)) func(t T) { return slice.ReverseReduce(fns, func(next HandlePipeFn[T], f HandleFn[T]) HandleFn[T] {
return func(t T) { return func(t T) {
next(f, t) next(f, t)
} }
@ -52,10 +51,12 @@ func (h *Handle) ReplacePipe(scene, pipeName string, pipe Pipe) error {
} }
func (h *Handle) PushHandler(pipScene string, scene string, fns ...HandleCall) { func (h *Handle) PushHandler(pipScene string, scene string, fns ...HandleCall) {
if _, ok := h.handlers[pipScene]; !ok { v, ok := handlerss.Load(pipScene)
h.handlers[pipScene] = make(map[string][]HandleCall) if !ok {
v = make(map[string][]HandleCall)
} }
h.handlers[pipScene][scene] = append(h.handlers[pipScene][scene], fns...) v[scene] = append(v[scene], fns...)
handlerss.Store(pipScene, v)
} }
func (h *Handle) PushRender(statsOrScene string, fns ...HandleCall) { func (h *Handle) PushRender(statsOrScene string, fns ...HandleCall) {
@ -65,27 +66,16 @@ func (h *Handle) PushDataHandler(scene string, fns ...HandleCall) {
h.PushHandler(constraints.PipeData, scene, fns...) h.PushHandler(constraints.PipeData, scene, fns...)
} }
func BuildPipe(pipeScene string, keyFn func(*Handle, string) string, fn func(*Handle, map[string][]HandleCall, string) []HandleCall) func(HandleFn[*Handle], *Handle) { func BuildHandlers(pipeScene string, keyFn func(*Handle, string) string,
handleHookFn func(*Handle, string,
map[string][]func(HandleCall) (HandleCall, bool),
map[string][]HandleCall) []HandleCall) func(HandleFn[*Handle], *Handle) {
pipeHandlerFn := reload.BuildMapFn[string]("pipeHandlers", BuildHandler(pipeScene, keyFn, handleHookFn))
return func(next HandleFn[*Handle], h *Handle) { return func(next HandleFn[*Handle], h *Handle) {
key := keyFn(h, pipeScene) key := keyFn(h, pipeScene)
handlers := reload.GetAnyValMapBy("pipeHandlers", key, h, func(h *Handle) []HandleCall { handlers := pipeHandlerFn(key, h)
conf := h.handleHook[pipeScene]
calls := fn(h, h.handlers[pipeScene], key)
calls = slice.FilterAndMap(calls, func(call HandleCall) (HandleCall, bool) {
ok := true
for _, hook := range conf {
call, ok = hook(call)
if !ok {
break
}
}
return call, ok
})
slice.Sort(calls, func(i, j HandleCall) bool {
return i.Order > j.Order
})
return calls
})
for _, handler := range handlers { for _, handler := range handlers {
handler.Fn(h) handler.Fn(h)
if h.abort { if h.abort {
@ -98,87 +88,141 @@ func BuildPipe(pipeScene string, keyFn func(*Handle, string) string, fn func(*Ha
} }
} }
func PipeKey(h *Handle, pipScene string) string { func BuildHandler(pipeScene string, keyFn func(*Handle, string) string,
key := str.Join("pipekey", "-", pipScene, "-", h.scene, "-", h.Stats) handleHook func(*Handle, string,
return h.ComponentFilterFnHook("pipeKey", key, pipScene) map[string][]func(HandleCall) (HandleCall, bool),
map[string][]HandleCall) []HandleCall) func(*Handle) []HandleCall {
return func(h *Handle) []HandleCall {
key := keyFn(h, pipeScene)
mut := reload.GetGlobeMutex()
mut.Lock()
pipeHookers, _ := handleHooks.Load(pipeScene)
pipeHandlers, _ := handlerss.Load(pipeScene)
mut.Unlock()
calls := handleHook(h, key, pipeHookers, pipeHandlers)
slice.SimpleSort(calls, slice.DESC, func(t HandleCall) float64 {
return t.Order
})
return calls
}
} }
func HookHandles(hooks map[string][]func(HandleCall) (HandleCall, bool),
handlers map[string][]HandleCall, sceneOrStats ...string) []HandleCall {
hookedHandlers := slice.FilterAndMap(sceneOrStats, func(k string) ([]HandleCall, bool) {
r := handlers[k]
for _, hook := range hooks[k] {
r = slice.FilterAndMap(r, hook)
}
return r, true
})
return slice.Decompress(hookedHandlers)
}
func PipeKey(h *Handle, pipScene string) string {
key := str.Join("pipekey", "-", pipScene, "-", h.scene, "-", h.Stats)
return h.DoActionFilter("pipeKey", key, pipScene)
}
var pipeInitFn = reload.BuildMapFn[string]("pipeInit", BuildPipe)
func Run(h *Handle, conf func(*Handle)) { func Run(h *Handle, conf func(*Handle)) {
if !helper.GetContextVal(h.C, "inited", false) { if !h.isInited {
InitHandle(conf, h) InitHandle(conf, h)
} }
reload.GetAnyValBys(str.Join("pipeInit-", h.scene), h, func(h *Handle) func(*Handle) { pipeInitFn(h.scene, h.scene)(h)
p := GetFn[Pipe]("pipe", constraints.AllScene) }
p = append(p, GetFn[Pipe]("pipe", h.scene)...)
pipes := slice.FilterAndMap(p, func(pipe Pipe) (Pipe, bool) { func BuildPipe(scene string) func(*Handle) {
var ok bool pipees := GetFn[Pipe]("pipe", constraints.AllScene)
hooks := GetFnHook[func(Pipe) (Pipe, bool)]("pipeHook", constraints.AllScene) pipees = append(pipees, GetFn[Pipe]("pipe", scene)...)
hooks = append(hooks, GetFnHook[func(Pipe) (Pipe, bool)]("pipeHook", h.scene)...) pipes := slice.FilterAndMap(pipees, func(pipe Pipe) (Pipe, bool) {
for _, fn := range hooks { var ok bool
pipe, ok = fn(pipe) mut := reload.GetGlobeMutex()
if !ok { mut.Lock()
return pipe, false hooks := GetFnHook[func(Pipe) (Pipe, bool)]("pipeHook", constraints.AllScene)
} hooks = append(hooks, GetFnHook[func(Pipe) (Pipe, bool)]("pipeHook", scene)...)
mut.Unlock()
for _, fn := range hooks {
pipe, ok = fn(pipe)
if !ok {
return pipe, false
} }
return pipe, pipe.Fn != nil }
}) return pipe, pipe.Fn != nil
slice.Sort(pipes, func(i, j Pipe) bool { })
return i.Order > j.Order slice.SimpleSort(pipes, slice.DESC, func(t Pipe) float64 {
}) return t.Order
arr := slice.Map(pipes, func(t Pipe) HandlePipeFn[*Handle] { })
return t.Fn
}) arr := slice.Map(pipes, func(t Pipe) HandlePipeFn[*Handle] {
return HandlePipe(NothingToDo, arr...) return t.Fn
})(h) })
return HandlePipe(NothingToDo, arr...)
} }
func MiddlewareKey(h *Handle, pipScene string) string { func MiddlewareKey(h *Handle, pipScene string) string {
return h.ComponentFilterFnHook("middleware", "middleware", pipScene) return h.DoActionFilter("middleware", str.Join("pipe-middleware-", h.scene), pipScene)
} }
func PipeMiddlewareHandle(h *Handle, middlewares map[string][]HandleCall, key string) (handlers []HandleCall) { func PipeMiddlewareHandle(h *Handle, key string,
handlers = append(handlers, middlewares[h.scene]...) hooks map[string][]func(HandleCall) (HandleCall, bool),
handlers = append(handlers, middlewares[constraints.AllScene]...) handlers map[string][]HandleCall) []HandleCall {
handlers = h.PipeHandleHook("PipeMiddlewareHandle", handlers, middlewares, key) hookedHandles := HookHandles(hooks, handlers, h.scene, constraints.AllScene)
return return h.PipeHandleHook("PipeMiddlewareHandle", hookedHandles, handlers, key)
} }
func PipeDataHandle(h *Handle, dataHandlers map[string][]HandleCall, key string) (handlers []HandleCall) { func PipeDataHandle(h *Handle, key string,
handlers = append(handlers, dataHandlers[h.scene]...) hooks map[string][]func(HandleCall) (HandleCall, bool),
handlers = append(handlers, dataHandlers[constraints.AllScene]...) handlers map[string][]HandleCall) []HandleCall {
handlers = h.PipeHandleHook("PipeDataHandle", handlers, dataHandlers, key) hookedHandles := HookHandles(hooks, handlers, h.scene, constraints.AllScene)
return return h.PipeHandleHook("PipeDataHandle", hookedHandles, handlers, key)
} }
func PipeRender(h *Handle, renders map[string][]HandleCall, key string) (handlers []HandleCall) { func PipeRender(h *Handle, key string,
handlers = append(handlers, renders[h.Stats]...) hooks map[string][]func(HandleCall) (HandleCall, bool),
handlers = append(handlers, renders[h.scene]...) handlers map[string][]HandleCall) []HandleCall {
handlers = append(handlers, renders[constraints.AllStats]...) hookedHandles := HookHandles(hooks, handlers, h.scene, h.Stats, constraints.AllScene, constraints.AllStats)
handlers = append(handlers, renders[constraints.AllScene]...) return h.PipeHandleHook("PipeRender", hookedHandles, handlers, key)
handlers = h.PipeHandleHook("PipeRender", handlers, renders, key)
return
} }
// DeleteHandle 写插件的时候用 // DeleteHandle 写插件的时候用
func (h *Handle) DeleteHandle(pipeScene string, name string) { func (h *Handle) DeleteHandle(pipeScene, scene, name string) {
h.handleHook[pipeScene] = append(h.handleHook[pipeScene], func(call HandleCall) (HandleCall, bool) { v, ok := handleHooks.Load(pipeScene)
if !ok {
v = make(map[string][]func(HandleCall) (HandleCall, bool))
}
v[scene] = append(v[scene], func(call HandleCall) (HandleCall, bool) {
return call, name != call.Name return call, name != call.Name
}) })
handleHooks.Store(pipeScene, v)
} }
// ReplaceHandle 写插件的时候用 // ReplaceHandle 写插件的时候用
func (h *Handle) ReplaceHandle(pipeScene, name string, fn HandleFn[*Handle]) { func (h *Handle) ReplaceHandle(pipeScene, scene, name string, fn HandleFn[*Handle]) {
h.handleHook[pipeScene] = append(h.handleHook[pipeScene], func(call HandleCall) (HandleCall, bool) { v, ok := handleHooks.Load(pipeScene)
if !ok {
v = make(map[string][]func(HandleCall) (HandleCall, bool))
}
v[scene] = append(v[scene], func(call HandleCall) (HandleCall, bool) {
if name == call.Name { if name == call.Name {
call.Fn = fn call.Fn = fn
} }
return call, true return call, true
}) })
handleHooks.Store(pipeScene, v)
} }
// HookHandle 写插件的时候用 // HookHandle 写插件的时候用
func (h *Handle) HookHandle(pipeScene string, hook func(HandleCall) (HandleCall, bool)) { func (h *Handle) HookHandle(pipeScene, scene string, hook func(HandleCall) (HandleCall, bool)) {
h.handleHook[pipeScene] = append(h.handleHook[pipeScene], hook) v, ok := handleHooks.Load(pipeScene)
if !ok {
v = make(map[string][]func(HandleCall) (HandleCall, bool))
}
v[scene] = append(v[scene], hook)
handleHooks.Store(pipeScene, v)
} }
func (h *Handle) PushPipeHandleHook(name string, fn ...func([]HandleCall) []HandleCall) error { func (h *Handle) PushPipeHandleHook(name string, fn ...func([]HandleCall) []HandleCall) error {
@ -193,11 +237,11 @@ func (h *Handle) PipeHandleHook(name string, calls []HandleCall, m map[string][]
} }
func InitPipe(h *Handle) { func InitPipe(h *Handle) {
h.PushPipe(constraints.Home, NewPipe(constraints.PipeMiddleware, 300, h.PushPipe(constraints.AllScene, NewPipe(constraints.PipeMiddleware, 300,
BuildPipe(constraints.PipeMiddleware, MiddlewareKey, PipeMiddlewareHandle))) BuildHandlers(constraints.PipeMiddleware, MiddlewareKey, PipeMiddlewareHandle)))
h.PushPipe(constraints.AllScene, NewPipe(constraints.PipeData, 200, h.PushPipe(constraints.AllScene, NewPipe(constraints.PipeData, 200,
BuildPipe(constraints.PipeData, PipeKey, PipeDataHandle))) BuildHandlers(constraints.PipeData, PipeKey, PipeDataHandle)))
h.PushPipe(constraints.AllScene, NewPipe(constraints.PipeRender, 100, h.PushPipe(constraints.AllScene, NewPipe(constraints.PipeRender, 100,
BuildPipe(constraints.PipeRender, PipeKey, PipeRender))) BuildHandlers(constraints.PipeRender, PipeKey, PipeRender)))
} }

View File

@ -1,13 +1,16 @@
package route package route
import ( import (
"github.com/fthvgb1/wp-go/app/cmd/reload"
"github.com/fthvgb1/wp-go/app/theme/wp" "github.com/fthvgb1/wp-go/app/theme/wp"
"github.com/fthvgb1/wp-go/cache/reload"
"github.com/fthvgb1/wp-go/helper/slice" "github.com/fthvgb1/wp-go/helper/slice"
"github.com/fthvgb1/wp-go/safety" "github.com/fthvgb1/wp-go/safety"
"net/http"
"regexp" "regexp"
) )
// Route
// Type value equal const or reg
type Route struct { type Route struct {
Path string Path string
Scene string Scene string
@ -20,18 +23,18 @@ var routeHook []func(Route) (Route, bool)
var regRoutes *safety.Map[string, *regexp.Regexp] var regRoutes *safety.Map[string, *regexp.Regexp]
var routes = func() *safety.Map[string, Route] { var routes = func() *safety.Map[string, Route] {
r := safety.NewMap[string, Route]() r := safety.NewMap[string, Route]()
reload.Push(func() { reload.Append(func() {
r.Flush() r.Flush()
regRoutes.Flush() regRoutes.Flush()
}) }, "wp-routers")
regRoutes = safety.NewMap[string, *regexp.Regexp]() regRoutes = safety.NewMap[string, *regexp.Regexp]()
return r return r
}() }()
// PushRoute path can be const or regex string // PushRoute path can be const or regex string
// //
// eg: `(?P<control>\w+)/(?P<method>\w+)`, route.Route{ // eg: `(?P<controller>\w+)/(?P<method>\w+)`, route.Route{
// Path: `(?P<control>\w+)/(?P<method>\w+)`, // Path: `(?P<controller>\w+)/(?P<method>\w+)`,
// Scene: constraints.Home, // Scene: constraints.Home,
// Method: []string{"GET"}, // Method: []string{"GET"},
// Type: "reg", // Type: "reg",
@ -70,60 +73,74 @@ func Hook(path string, fn func(Route) Route) {
return r, path == r.Path return r, path == r.Path
}) })
} }
var RegRouteFn = reload.BuildValFnWithAnyParams("regexRoute", RegRouteHook)
func RegRouteHook(_ ...any) func() (map[string]Route, map[string]*regexp.Regexp) {
m := map[string]Route{}
rrs := map[string]*regexp.Regexp{}
routes.Range(func(key string, value Route) bool {
vv, _ := regRoutes.Load(key)
if len(routeHook) > 0 {
for _, fn := range routeHook {
v, ok := fn(value)
if !ok {
continue
}
m[v.Path] = v
if v.Type != "reg" {
continue
}
if v.Path != key {
vvv, err := regexp.Compile(v.Path)
if err != nil {
panic(err)
}
vv = vvv
}
rrs[v.Path] = vv
}
} else {
m[key] = value
rrs[key] = vv
}
return true
})
return func() (map[string]Route, map[string]*regexp.Regexp) {
return m, rrs
}
}
func ResolveRoute(h *wp.Handle) { func ResolveRoute(h *wp.Handle) {
requestURI := h.C.Request.RequestURI requestURI := h.C.Request.RequestURI
rs, rrs := reload.GetAnyValBys("route", rs, rrs := RegRouteFn()()
struct{}{},
func(_ struct{}) func() (map[string]Route, map[string]*regexp.Regexp) {
m := map[string]Route{}
rrs := map[string]*regexp.Regexp{}
routes.Range(func(key string, value Route) bool {
vv, _ := regRoutes.Load(key)
if len(routeHook) > 0 {
for _, fn := range routeHook {
v, ok := fn(value)
if ok {
m[v.Path] = v
if v.Type == "reg" {
if v.Path != key {
vvv, err := regexp.Compile(v.Path)
if err != nil {
panic(err)
}
vv = vvv
}
rrs[v.Path] = vv
}
}
}
} else {
m[key] = value
rrs[key] = vv
}
return true
})
return func() (map[string]Route, map[string]*regexp.Regexp) {
return m, rrs
}
})()
v, ok := rs[requestURI] v, ok := rs[requestURI]
if ok && slice.IsContained(v.Method, h.C.Request.Method) { if ok && slice.IsContained(v.Method, h.C.Request.Method) {
h.SetScene(v.Scene) h.SetScene(v.Scene)
wp.Run(h, nil) wp.Run(h, nil)
h.Abort()
return return
} }
for path, reg := range rrs { for path, reg := range rrs {
r := reg.FindAllStringSubmatch(requestURI, -1) r := reg.FindStringSubmatch(requestURI)
if len(r) < 1 { if len(r) < 1 {
return return
} }
rr := rs[path] rr := rs[path]
if slice.IsContained(rr.Method, h.C.Request.Method) { if slice.IsContained(rr.Method, h.C.Request.Method) {
h.SetScene(rr.Scene) h.SetScene(rr.Scene)
h.C.Set("route", r) for i, name := range reg.SubexpNames() {
if name == "" {
continue
}
h.C.AddParam(name, r[i])
}
h.C.Set("regRoute", reg)
h.C.Set("regRouteRes", r)
wp.Run(h, nil) wp.Run(h, nil)
h.Abort()
return return
} }
} }
h.C.Status(http.StatusNotFound)
h.Abort()
} }

View File

@ -9,7 +9,7 @@ import (
"strings" "strings"
) )
var sizes = []string{"site_icon-270", "site_icon-32", "site_icon-192", "site_icon-180"} var iconSizes = []string{"site_icon-270", "site_icon-32", "site_icon-192", "site_icon-180"}
func CalSiteIcon(h *Handle) (r string) { func CalSiteIcon(h *Handle) (r string) {
id := str.ToInteger[uint64](wpconfig.GetOption("site_icon"), 0) id := str.ToInteger[uint64](wpconfig.GetOption("site_icon"), 0)
@ -21,7 +21,7 @@ func CalSiteIcon(h *Handle) (r string) {
return return
} }
m := strings.Join(strings.Split(icon.AttachmentMetadata.File, "/")[:2], "/") m := strings.Join(strings.Split(icon.AttachmentMetadata.File, "/")[:2], "/")
size := slice.FilterAndMap(sizes, func(t string) (string, bool) { size := slice.FilterAndMap(iconSizes, func(t string) (string, bool) {
s, ok := icon.AttachmentMetadata.Sizes[t] s, ok := icon.AttachmentMetadata.Sizes[t]
if !ok { if !ok {
return "", false return "", false

View File

@ -3,45 +3,46 @@ package wp
import ( import (
"fmt" "fmt"
"github.com/elliotchance/phpserialize" "github.com/elliotchance/phpserialize"
"github.com/fthvgb1/wp-go/app/cmd/reload"
"github.com/fthvgb1/wp-go/app/pkg/cache" "github.com/fthvgb1/wp-go/app/pkg/cache"
"github.com/fthvgb1/wp-go/app/pkg/logs" "github.com/fthvgb1/wp-go/app/pkg/logs"
"github.com/fthvgb1/wp-go/app/pkg/models" "github.com/fthvgb1/wp-go/app/pkg/models"
"github.com/fthvgb1/wp-go/app/wpconfig" "github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/helper/maps" "github.com/fthvgb1/wp-go/cache/reload"
"github.com/fthvgb1/wp-go/helper/slice" "github.com/fthvgb1/wp-go/helper/slice"
str "github.com/fthvgb1/wp-go/helper/strings" str "github.com/fthvgb1/wp-go/helper/strings"
) )
func (h *Handle) StickPosts() []models.Posts { var GetStickPosts = reload.BuildValFnWithConfirm("stickPostsSlice", ParseStickPosts)
return reload.GetAnyValBys("stickPostsSlice", h, func(h *Handle) (r []models.Posts) {
v := wpconfig.GetOption("sticky_posts") func ParseStickPosts(h *Handle) (r []models.Posts, ok bool) {
if v == "" { v := wpconfig.GetOption("sticky_posts")
return if v == "" {
}
array, err := phpserialize.UnmarshalIndexedArray([]byte(v))
if err != nil {
logs.Error(err, "解析option sticky_posts错误", v)
return
}
r = slice.FilterAndMap(array, func(t any) (models.Posts, bool) {
id := str.ToInt[uint64](fmt.Sprintf("%v", t))
post, err := cache.GetPostById(h.C, id)
post.IsSticky = true
return post, err == nil
})
return return
}
array, err := phpserialize.UnmarshalIndexedArray([]byte(v))
if err != nil {
logs.Error(err, "解析option sticky_posts错误", v)
return
}
r = slice.FilterAndMap(array, func(t any) (models.Posts, bool) {
id := str.ToInt[uint64](fmt.Sprintf("%v", t))
post, err := cache.GetPostById(h.C, id)
post.IsSticky = true
return post, err == nil
}) })
ok = true
return
} }
func (h *Handle) StickMapPosts() map[uint64]models.Posts { var GetStickMapPosts = reload.BuildValFn("stickPostsMap", StickMapPosts)
return reload.GetAnyValBys("stickPostsMap", h, func(h *Handle) map[uint64]models.Posts {
return slice.SimpleToMap(h.StickPosts(), func(v models.Posts) uint64 { func StickMapPosts(h *Handle) map[uint64]models.Posts {
return v.Id return slice.SimpleToMap(GetStickPosts(h), func(v models.Posts) uint64 {
}) return v.Id
}) })
} }
func (h *Handle) IsStick(id uint64) bool { func (h *Handle) IsStick(id uint64) bool {
return maps.IsExists(h.StickMapPosts(), id) _, ok := GetStickMapPosts(h)[id]
return ok
} }

View File

@ -1,8 +1,6 @@
{{define "common/head"}} {{define "common/head"}}
{{if .headScript}} {{ callFuncString .calComponent "headScript"}}
{{.headScript|unescaped}}
{{end}}
{{if .externHead}} {{if .externHead}}
{{.externHead|unescaped}} {{.externHead|unescaped}}
@ -10,9 +8,7 @@
{{end}} {{end}}
{{define "common/footer"}} {{define "common/footer"}}
{{if .footerScript}} {{ callFuncString .calComponent "footerScript"}}
{{.footerScript|unescaped}}
{{end}}
{{if .externFooter}} {{if .externFooter}}
{{.externFooter|unescaped}} {{.externFooter|unescaped}}
{{end}} {{end}}
@ -20,7 +16,17 @@
{{define "common/sidebarWidget"}} {{define "common/sidebarWidget"}}
{{if .sidebarsWidgets}} {{ callFuncString .calComponent "sidebarsWidgets"}}
{{.sidebarsWidgets|unescaped}} {{end}}
{{define "common/colophon"}}
{{if .colophon}}
{{.colophon|unescaped}}
{{else}}
<footer id="colophon" class="site-footer">
<div class="site-info">
<a href="https://github.com/fthvgb1/wp-go" class="imprint">自豪地采用 wp-go</a>
</div>
</footer>
{{end}} {{end}}
{{end}} {{end}}

View File

@ -1,44 +1,47 @@
package wp package wp
import ( import (
"github.com/fthvgb1/wp-go/app/cmd/reload" "errors"
"github.com/fthvgb1/wp-go/app/pkg/config" "github.com/fthvgb1/wp-go/app/pkg/config"
"github.com/fthvgb1/wp-go/app/pkg/constraints" "github.com/fthvgb1/wp-go/app/pkg/constraints"
"github.com/fthvgb1/wp-go/app/pkg/logs" "github.com/fthvgb1/wp-go/app/pkg/logs"
"github.com/fthvgb1/wp-go/app/plugins/wphandle/apply" "github.com/fthvgb1/wp-go/app/plugins/wphandle/apply"
"github.com/fthvgb1/wp-go/app/wpconfig" "github.com/fthvgb1/wp-go/app/wpconfig"
"github.com/fthvgb1/wp-go/cache/reload"
"github.com/fthvgb1/wp-go/helper/maps" "github.com/fthvgb1/wp-go/helper/maps"
str "github.com/fthvgb1/wp-go/helper/strings" str "github.com/fthvgb1/wp-go/helper/strings"
"github.com/fthvgb1/wp-go/safety"
"github.com/gin-contrib/sessions" "github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"html/template" "html/template"
"net/http" "net/http"
"os" "os"
"strings"
) )
type Handle struct { type Handle struct {
Index *IndexHandle C *gin.Context
Detail *DetailHandle theme string
C *gin.Context isInited bool
theme string Session sessions.Session
Session sessions.Session ginH gin.H
ginH gin.H password string
password string scene string
scene string Code int
Code int Stats string
Stats string templ string
templ string themeMods wpconfig.ThemeMods
components map[string]map[string][]Components[string] err error
componentHook map[string][]func(Components[string]) (Components[string], bool) errLevel int8
themeMods wpconfig.ThemeMods abort bool
handlers map[string]map[string][]HandleCall stopPipe bool
handleHook map[string][]func(HandleCall) (HandleCall, bool) }
err error
abort bool var handlerss = safety.NewMap[string, map[string][]HandleCall]()
stopPipe bool var handleHooks = safety.NewMap[string, map[string][]func(HandleCall) (HandleCall, bool)]()
componentsArgs map[string]any
componentFilterFn map[string][]func(*Handle, string, ...any) string func (h *Handle) Theme() string {
template *template.Template return h.theme
} }
func (h *Handle) GinH() gin.H { func (h *Handle) GinH() gin.H {
@ -49,28 +52,20 @@ func (h *Handle) SetScene(scene string) {
h.scene = scene h.scene = scene
} }
func (h *Handle) Components() map[string]map[string][]Components[string] { func (h *Handle) Components() *safety.Map[string, map[string][]Components[string]] {
return h.components return handleComponents
} }
func (h *Handle) ComponentHook() map[string][]func(Components[string]) (Components[string], bool) { func (h *Handle) ComponentHook() *safety.Map[string, map[string][]func(Components[string]) (Components[string], bool)] {
return h.componentHook return handleComponentHook
} }
func (h *Handle) Handlers() map[string]map[string][]HandleCall { func (h *Handle) Handlers() *safety.Map[string, map[string][]HandleCall] {
return h.handlers return handlerss
} }
func (h *Handle) HandleHook() map[string][]func(HandleCall) (HandleCall, bool) { func (h *Handle) HandleHook() *safety.Map[string, map[string][]func(HandleCall) (HandleCall, bool)] {
return h.handleHook return handleHooks
}
func (h *Handle) SetTemplate(template *template.Template) {
h.template = template
}
func (h *Handle) Template() *template.Template {
return h.template
} }
type HandlePlugins map[string]HandleFn[*Handle] type HandlePlugins map[string]HandleFn[*Handle]
@ -92,44 +87,50 @@ type HandleCall struct {
Name string Name string
} }
func InitHandle(fn func(*Handle), h *Handle) { var isFirstRequest = true
var inited = false
hh := reload.GetAnyValBys("themeArgAndConfig", h, func(h *Handle) Handle { func SetConfigHandle(a ...any) Handle {
h.components = make(map[string]map[string][]Components[string]) configFn := a[0].(func(*Handle))
h.componentsArgs = make(map[string]any) hh := a[1].(*Handle)
h.componentFilterFn = make(map[string][]func(*Handle, string, ...any) string) h := &Handle{}
h.handlers = make(map[string]map[string][]HandleCall) handleComponents.Flush()
h.handleHook = make(map[string][]func(HandleCall) (HandleCall, bool)) componentsArgs.Flush()
h.ginH = gin.H{} handleComponentHook.Flush()
fnMap = map[string]map[string]any{} componentFilterFns.Flush()
fnHook = map[string]map[string]any{} handlerss.Flush()
fn(h) handleHooks.Flush()
v := apply.UsePlugins() h.ginH = gin.H{}
pluginFn, ok := v.(func(*Handle)) fnMap.Flush()
if ok { fnHook.Flush()
pluginFn(h) if isFirstRequest {
} isFirstRequest = false
h.C.Set("inited", true) } else {
inited = true reload.Reload()
return *h }
}) h.C = hh.C
h.theme = hh.theme
configFn(h)
v := apply.GetPlugins()
pluginFn, ok := v.(func(*Handle))
if ok {
pluginFn(h)
}
return *h
}
var GetInitHandleFn = reload.BuildValFnWithAnyParams("themeArgAndConfig", SetConfigHandle, false)
func InitHandle(configFn func(*Handle), h *Handle) {
hh := GetInitHandleFn(configFn, h)
mods, err := wpconfig.GetThemeMods(h.theme)
logs.IfError(err, "获取mods失败")
h.themeMods = mods
h.ginH = maps.Copy(hh.ginH) h.ginH = maps.Copy(hh.ginH)
h.ginH["calPostClass"] = postClass(h) h.ginH["calPostClass"] = postClass(h)
h.ginH["calBodyClass"] = bodyClass(h) h.ginH["calBodyClass"] = bodyClass(h)
h.ginH["customLogo"] = customLogo(h) h.ginH["customLogo"] = customLogo(h)
if inited { h.ginH["calComponent"] = CalComponent(h)
return h.isInited = true
}
h.components = hh.components
h.Index.postsPlugin = hh.Index.postsPlugin
h.Index.pageEle = hh.Index.pageEle
h.Detail.CommentRender = hh.Detail.CommentRender
h.handlers = hh.handlers
h.handleHook = hh.handleHook
h.componentHook = hh.componentHook
h.componentsArgs = hh.componentsArgs
h.componentFilterFn = hh.componentFilterFn
h.C.Set("inited", true)
} }
func (h *Handle) Abort() { func (h *Handle) Abort() {
@ -151,13 +152,25 @@ func (h *Handle) Err() error {
return h.err return h.err
} }
func (h *Handle) SetErr(err error) { func (h *Handle) SetErr(err error, level int8) {
h.err = err h.err = errors.Join(err)
h.errLevel = level
}
func (h *Handle) ErrLevel() int8 {
return h.errLevel
}
func (h *Handle) SetErrLevel(errLevel int8) {
h.errLevel = errLevel
} }
func (h *Handle) SetTempl(templ string) { func (h *Handle) SetTempl(templ string) {
h.templ = templ h.templ = templ
} }
func (h *Handle) GetTempl() string {
return h.templ
}
func (h *Handle) Scene() string { func (h *Handle) Scene() string {
return h.scene return h.scene
@ -171,15 +184,12 @@ func (h *Handle) SetData(k string, v any) {
} }
func NewHandle(c *gin.Context, scene string, theme string) *Handle { func NewHandle(c *gin.Context, scene string, theme string) *Handle {
mods, err := wpconfig.GetThemeMods(theme)
logs.IfError(err, "获取mods失败")
return &Handle{ return &Handle{
C: c, C: c,
theme: theme, theme: theme,
Session: sessions.Default(c), Session: sessions.Default(c),
scene: scene, scene: scene,
Stats: constraints.Ok, Stats: constraints.Ok,
themeMods: mods,
} }
} }
@ -224,7 +234,12 @@ func (h *Handle) RenderHtml(t *template.Template, statsCode int, name string) {
header["Content-Type"] = htmlContentType header["Content-Type"] = htmlContentType
} }
h.C.Status(statsCode) h.C.Status(statsCode)
err := t.ExecuteTemplate(h.C.Writer, name, h.ginH) var err error
if name == "" {
err = t.Execute(h.C.Writer, h.ginH)
} else {
err = t.ExecuteTemplate(h.C.Writer, name, h.ginH)
}
h.Abort() h.Abort()
if err != nil { if err != nil {
panic(err) panic(err)
@ -239,8 +254,8 @@ func (h *Handle) PushHandlers(pipeScene string, call HandleCall, statsOrScene ..
func (h *Handle) CommonComponents() { func (h *Handle) CommonComponents() {
h.PushCacheGroupHeadScript(constraints.AllScene, "siteIconAndCustomCss", 0, CalSiteIcon, CalCustomCss) h.PushCacheGroupHeadScript(constraints.AllScene, "siteIconAndCustomCss", 0, CalSiteIcon, CalCustomCss)
h.PushRender(constraints.AllStats, NewHandleFn(CalComponents, 10.001, "wp.CalComponents"))
h.PushRender(constraints.AllStats, NewHandleFn(PreRenderTemplate, 0, "wp.PreRenderTemplate")) h.PushRender(constraints.AllStats, NewHandleFn(PreRenderTemplate, 0, "wp.PreRenderTemplate"))
ReplyCommentJs(h)
AdditionScript(h) AdditionScript(h)
} }
@ -281,3 +296,10 @@ func NewHandleFn(fn HandleFn[*Handle], order float64, name string) HandleCall {
func NothingToDo(h *Handle) { func NothingToDo(h *Handle) {
h.Abort() h.Abort()
} }
func (h *Handle) IsHttps() bool {
if h.C.Request.TLS != nil {
return true
}
return "https" == strings.ToLower(h.C.Request.Header.Get("X-Forwarded-Proto"))
}

View File

@ -39,7 +39,13 @@ func GetOption(k string) string {
if ok { if ok {
return v return v
} }
vv, err := model.GetField[models.Options](ctx, "option_value", model.Conditions(model.Where(model.SqlBuilder{{"option_name", k}}))) vv, err := model.GetField[models.Options](ctx, "option_value",
model.Conditions(
model.Where(
model.SqlBuilder{{"option_name", k}},
),
),
)
options.Store(k, vv) options.Store(k, vv)
if err != nil { if err != nil {
return "" return ""

View File

@ -3,10 +3,10 @@ package wpconfig
import ( import (
"embed" "embed"
"fmt" "fmt"
"github.com/fthvgb1/wp-go/app/cmd/reload"
"github.com/fthvgb1/wp-go/app/phphelper" "github.com/fthvgb1/wp-go/app/phphelper"
"github.com/fthvgb1/wp-go/app/pkg/logs" "github.com/fthvgb1/wp-go/app/pkg/logs"
"github.com/fthvgb1/wp-go/app/pkg/models" "github.com/fthvgb1/wp-go/app/pkg/models"
"github.com/fthvgb1/wp-go/cache/reload"
"github.com/fthvgb1/wp-go/helper/maps" "github.com/fthvgb1/wp-go/helper/maps"
"github.com/fthvgb1/wp-go/safety" "github.com/fthvgb1/wp-go/safety"
"path/filepath" "path/filepath"
@ -73,6 +73,7 @@ func Thumbnail(metadata models.WpAttachmentMetadata, Type, host string, except .
up := strings.Split(metadata.File, "/") up := strings.Split(metadata.File, "/")
if metadata.File != "" && Type == "full" { if metadata.File != "" && Type == "full" {
mimeType := metadata.Sizes["thumbnail"].MimeType mimeType := metadata.Sizes["thumbnail"].MimeType
metadata.Sizes = maps.Copy(metadata.Sizes)
metadata.Sizes["full"] = models.MetaDataFileSize{ metadata.Sizes["full"] = models.MetaDataFileSize{
File: filepath.Base(metadata.File), File: filepath.Base(metadata.File),
Width: metadata.Width, Width: metadata.Width,
@ -111,10 +112,10 @@ func Thumbnail(metadata models.WpAttachmentMetadata, Type, host string, except .
var themeModes = func() *safety.Map[string, ThemeMods] { var themeModes = func() *safety.Map[string, ThemeMods] {
m := safety.NewMap[string, ThemeMods]() m := safety.NewMap[string, ThemeMods]()
themeModsRaw = safety.NewMap[string, map[string]any]() themeModsRaw = safety.NewMap[string, map[string]any]()
reload.Push(func() { reload.Append(func() {
m.Flush() m.Flush()
themeModsRaw.Flush() themeModsRaw.Flush()
}) }, "theme-modes")
return m return m
}() }()

44
cache/cache.go vendored
View File

@ -2,15 +2,49 @@ package cache
import ( import (
"context" "context"
"sync"
"time" "time"
) )
type Cache[K comparable, V any] interface { type Cache[K comparable, V any] interface {
Get(ctx context.Context, key K) (V, bool) Get(ctx context.Context, key K) (V, bool)
Set(ctx context.Context, key K, val V, expire time.Duration) Set(ctx context.Context, key K, val V)
Ttl(ctx context.Context, key K, expire time.Duration) time.Duration GetExpireTime(ctx context.Context) time.Duration
Ver(ctx context.Context, key K) int Ttl(ctx context.Context, key K) time.Duration
Flush(ctx context.Context) Flush(ctx context.Context)
Delete(ctx context.Context, key K) Del(ctx context.Context, key ...K)
ClearExpired(ctx context.Context, expire time.Duration) ClearExpired(ctx context.Context)
}
type Expend[K comparable, V any] interface {
Gets(ctx context.Context, k []K) (map[K]V, error)
Sets(ctx context.Context, m map[K]V)
}
type SetTime interface {
SetExpiredTime(func() time.Duration)
}
type AnyCache[T any] interface {
Get(ctx context.Context) (T, bool)
Set(ctx context.Context, v T)
Flush(ctx context.Context)
GetLastSetTime(ctx context.Context) time.Time
}
type Refresh[K comparable, V any] interface {
Refresh(ctx context.Context, k K, a ...any)
}
type RefreshVar[T any] interface {
Refresh(ctx context.Context, a ...any)
}
type Lockss[K comparable] interface {
GetLock(ctx context.Context, gMut *sync.Mutex, k ...K) *sync.Mutex
}
type LockFn[K comparable] func(ctx context.Context, gMut *sync.Mutex, k ...K) *sync.Mutex
type LocksNum interface {
SetLockNum(num int)
} }

163
cache/cachemanager/manger.go vendored Normal file
View File

@ -0,0 +1,163 @@
package cachemanager
import (
"context"
"github.com/fthvgb1/wp-go/cache"
"github.com/fthvgb1/wp-go/cache/reload"
"github.com/fthvgb1/wp-go/helper"
"github.com/fthvgb1/wp-go/helper/slice/mockmap"
str "github.com/fthvgb1/wp-go/helper/strings"
"github.com/fthvgb1/wp-go/safety"
"runtime"
"sync"
"time"
)
var mutex sync.Mutex
type Fn func(context.Context)
type clearExpired interface {
ClearExpired(ctx context.Context)
}
var clears = safety.NewVar(mockmap.Map[string, Fn]{})
var flushes = safety.NewVar(mockmap.Map[string, Fn]{})
func Flush() {
ctx := context.WithValue(context.Background(), "execFlushBy", "mangerFlushFn")
for _, f := range flushes.Load() {
f.Value(ctx)
}
}
func Flushes(ctx context.Context, names ...string) {
execute(ctx, flushes, names...)
}
func execute(ctx context.Context, q *safety.Var[mockmap.Map[string, Fn]], names ...string) {
queues := q.Load()
for _, name := range names {
queue := queues.Get(name)
if queue.Value != nil {
queue.Value(ctx)
}
}
}
func parseArgs(args ...any) (string, func() time.Duration) {
var name string
var fn func() time.Duration
for _, arg := range args {
v, ok := arg.(string)
if ok {
name = v
continue
}
vv, ok := arg.(func() time.Duration)
if ok {
fn = vv
}
}
return name, fn
}
func buildLockFn[K comparable](args ...any) cache.LockFn[K] {
lockFn := helper.ParseArgs(cache.LockFn[K](nil), args...)
name := helper.ParseArgs("", args...)
num := helper.ParseArgs(runtime.NumCPU(), args...)
loFn := func() int {
return num
}
loFn = helper.ParseArgs(loFn, args...)
if name != "" {
loFn = reload.BuildFnVal(str.Join("cachesLocksNum-", name), num, loFn)
}
if lockFn == nil {
looo := helper.ParseArgs(cache.Lockss[K](nil), args...)
if looo != nil {
lockFn = looo.GetLock
loo, ok := any(looo).(cache.LocksNum)
if ok && loo != nil {
loo.SetLockNum(num)
}
} else {
lo := cache.NewLocks[K](loFn)
lockFn = lo.GetLock
PushOrSetFlush(mockmap.Item[string, Fn]{
Name: name,
Value: lo.Flush,
})
}
}
return lockFn
}
func SetExpireTime(c cache.SetTime, name string, expireTime time.Duration, expireTimeFn func() time.Duration) {
if name == "" {
return
}
fn := reload.BuildFnVal(str.Join("cacheManger-", name, "-expiredTime"), expireTime, expireTimeFn)
c.SetExpiredTime(fn)
}
func ChangeExpireTime(t time.Duration, coverConf bool, name ...string) {
for _, s := range name {
reload.SetFnVal(s, t, coverConf)
}
}
func pushOrSet(q *safety.Var[mockmap.Map[string, Fn]], queues ...mockmap.Item[string, Fn]) {
mutex.Lock()
defer mutex.Unlock()
qu := q.Load()
for _, queue := range queues {
v := qu.Get(queue.Name)
if v.Value != nil {
qu.Set(queue.Name, queue.Value)
} else {
qu = append(qu, queue)
}
}
q.Store(qu)
}
// PushOrSetFlush will execute flush func when call Flush or Flushes
func PushOrSetFlush(queues ...mockmap.Item[string, Fn]) {
pushOrSet(flushes, queues...)
}
// PushOrSetClearExpired will execute clearExpired func when call ClearExpired or ClearExpireds
func PushOrSetClearExpired(queues ...mockmap.Item[string, Fn]) {
pushOrSet(clears, queues...)
}
func del(q *safety.Var[mockmap.Map[string, Fn]], names ...string) {
mutex.Lock()
defer mutex.Unlock()
queues := q.Load()
for _, name := range names {
queues.Del(name)
}
q.Store(queues)
}
func DelFlush(names ...string) {
del(flushes, names...)
}
func DelClearExpired(names ...string) {
del(clears, names...)
}
func ClearExpireds(ctx context.Context, names ...string) {
execute(ctx, clears, names...)
}
func ClearExpired() {
ctx := context.WithValue(context.Background(), "execClearExpired", "mangerClearExpiredFn")
for _, queue := range clears.Load() {
queue.Value(ctx)
}
}

193
cache/cachemanager/manger_test.go vendored Normal file
View File

@ -0,0 +1,193 @@
package cachemanager
import (
"context"
"fmt"
"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/taskPools"
"reflect"
"strings"
"testing"
"time"
)
var ctx = context.Background()
func TestFlushMapVal(t *testing.T) {
_ = number.Range(1, 5, 0)
t.Run("t1", func(t *testing.T) {
count := 0
vv := NewMemoryMapCache(func(ctx2 context.Context, ks []int, a ...any) (map[int]int, error) {
r := make(map[int]int)
for _, k := range ks {
r[k] = k * k
}
count++
return r, nil
}, nil, time.Second, "test")
gets, err := GetBatchBy[int]("test", ctx, number.Range(1, 10), time.Second)
if err != nil {
t.Fatal(t, "err:", err)
}
p := taskPools.NewPools(10)
for i := 0; i < 20; i++ {
i := i
p.Execute(func() {
if i%2 == 0 {
vv.Get(ctx, 5)
} else {
vv.Set(ctx, i, i)
}
})
}
p.Wait()
fmt.Println(gets, count)
DelMapCacheVal("test", 3, 4)
fmt.Println(vv.Get(ctx, 3))
fmt.Println(vv.Get(ctx, 4))
get, err := GetBy[int]("test", ctx, 3, time.Second)
if err != nil {
t.Fatal(t, "err", err)
}
fmt.Println(get, count)
fmt.Println(vv.Get(ctx, 5))
Flushes(ctx, "test")
fmt.Println(vv.Get(ctx, 5))
fmt.Println(vv.Get(ctx, 6))
//fmt.Println(GetVarCache("test"))
})
}
func TestSetExpireTime(t *testing.T) {
t.Run("t1", func(t *testing.T) {
c := NewMemoryMapCache[string, string](func(ctx2 context.Context, strings []string, a ...any) (map[string]string, error) {
return slice.ToMap(strings, func(v string) (string, string) {
return v, str.Join(v, "__", v)
}, false), nil
}, nil, time.Second, "xx")
c.Set(ctx, "xx", "yy")
fmt.Println(c.Get(ctx, "xx"))
time.Sleep(time.Second)
fmt.Println(c.Get(ctx, "xx"))
ChangeExpireTime(3*time.Second, true, "xx")
c.Set(ctx, "xx", "yyy")
time.Sleep(time.Second)
fmt.Println(c.Get(ctx, "xx"))
time.Sleep(3 * time.Second)
fmt.Println(c.Get(ctx, "xx"))
cc, _ := GetMapCache[string, string]("xx")
fmt.Println(reflect.DeepEqual(c, cc))
cc.Set(ctx, "fff", "xxxx")
cc.Set(ctx, "ffx", "eex")
cc.Set(ctx, "ww", "vv")
m, err := cc.GetBatchToMap(ctx, []string{"fff", "ffx", "ww", "kkkk"}, time.Second)
fmt.Println(m, err)
fmt.Println(GetBatchByToMap[string]("xx", ctx, []string{"fff", "ffx", "ww", "kkkk"}, time.Second))
v := NewVarMemoryCache(func(ct context.Context, a ...any) (string, error) {
return "ssss", nil
}, 3*time.Second, "ff")
vv, _ := GetVarCache[string]("ff")
fmt.Println(reflect.DeepEqual(v, vv))
})
}
func TestSetMapCache(t *testing.T) {
t.Run("t1", func(t *testing.T) {
x := NewMemoryMapCache(nil, func(ctx2 context.Context, k string, a ...any) (string, error) {
fmt.Println("memory cache")
return strings.Repeat(k, 2), nil
}, time.Hour, "test")
fmt.Println(GetBy[string]("test", ctx, "test", time.Second))
NewMapCache[string, string](xx[string, string]{m: map[string]string{}}, nil, func(ctx2 context.Context, k string, a ...any) (string, error) {
fmt.Println("other cache drives. eg: redis,file.....")
return strings.Repeat(k, 2), nil
}, "test", time.Hour)
if err := SetMapCache("kkk", x); err != nil {
t.Errorf("SetMapCache() error = %v, wantErr %v", err, nil)
}
fmt.Println(GetBy[string]("test", ctx, "test", time.Second))
})
}
type xx[K comparable, V any] struct {
m map[K]V
}
func (x xx[K, V]) Get(ctx context.Context, key K) (V, bool) {
v, ok := x.m[key]
return v, ok
}
func (x xx[K, V]) Set(ctx context.Context, key K, val V) {
x.m[key] = val
}
func (x xx[K, V]) GetExpireTime(ctx context.Context) time.Duration {
//TODO implement me
panic("implement me")
}
func (x xx[K, V]) Ttl(ctx context.Context, key K) time.Duration {
//TODO implement me
panic("implement me")
}
func (x xx[K, V]) Flush(ctx context.Context) {
//TODO implement me
panic("implement me")
}
func (x xx[K, V]) Del(ctx context.Context, key ...K) {
//TODO implement me
panic("implement me")
}
func (x xx[K, V]) ClearExpired(ctx context.Context) {
//TODO implement me
panic("implement me")
}
func TestSetVarCache(t *testing.T) {
t.Run("t1", func(t *testing.T) {
bak := NewVarMemoryCache(func(ctx2 context.Context, a ...any) (string, error) {
fmt.Println("memory cache")
return "xxx", nil
}, time.Hour, "test")
fmt.Println(GetVarVal[string]("test", ctx, time.Second))
NewVarCache[string](oo[string]{}, func(ctx2 context.Context, a ...any) (string, error) {
fmt.Println("other cache drives. eg: redis,file.....")
return "ooo", nil
}, "test")
if err := SetVarCache("xx", bak); err != nil {
t.Errorf("SetVarCache() error = %v, wantErr %v", err, nil)
}
fmt.Println(GetVarVal[string]("test", ctx, time.Second))
})
}
type oo[T any] struct {
val T
}
func (o oo[T]) Get(ctx context.Context) (T, bool) {
return o.val, false
}
func (o oo[T]) Set(ctx context.Context, v T) {
o.val = v
}
func (o oo[T]) Flush(ctx context.Context) {
//TODO implement me
panic("implement me")
}
func (o oo[T]) GetLastSetTime(ctx context.Context) time.Time {
//TODO implement me
panic("implement me")
}

148
cache/cachemanager/mapcache.go vendored Normal file
View File

@ -0,0 +1,148 @@
package cachemanager
import (
"context"
"errors"
"github.com/fthvgb1/wp-go/cache"
"github.com/fthvgb1/wp-go/helper"
"github.com/fthvgb1/wp-go/helper/slice/mockmap"
str "github.com/fthvgb1/wp-go/helper/strings"
"github.com/fthvgb1/wp-go/safety"
"time"
)
var mapDelFuncs = safety.NewMap[string, func(any)]()
var mapCache = safety.NewMap[string, any]()
func SetMapCache[K comparable, V any](name string, ca *cache.MapCache[K, V]) error {
v, ok := mapCache.Load(name)
if !ok {
mapCache.Store(name, ca)
return nil
}
_, ok = v.(*cache.MapCache[K, V])
if !ok {
return errors.New(str.Join("cache ", name, " type err"))
}
mapCache.Store(name, ca)
return nil
}
// PushMangerMap will del mapCache val with name When call DelMapCacheVal
func PushMangerMap[K comparable, V any](name string, m *cache.MapCache[K, V]) {
if name == "" {
return
}
mapCache.Store(name, m)
mapDelFuncs.Store(name, func(a any) {
k, ok := a.([]K)
if ok && len(k) > 0 {
mm, ok := mapCache.Load(name)
if !ok {
return
}
c, ok := mm.(*cache.MapCache[K, V])
if !ok {
return
}
ctx := context.WithValue(context.Background(), "ctx", "registerFlush")
c.Del(ctx, k...)
}
})
}
func GetBy[T any, K comparable](name string, ct context.Context, key K, timeout time.Duration, params ...any) (r T, err error) {
ct = context.WithValue(ct, "getCache", name)
ca, err := getMap[K, T](name)
if err != nil {
return r, err
}
vv, err := ca.GetCache(ct, key, timeout, params...)
if err != nil {
return r, err
}
r = vv
return
}
func getMap[K comparable, T any](name string) (*cache.MapCache[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.MapCache[K, T])
if !ok {
return nil, errors.New(str.Join("cache ", name, " type error"))
}
return vk, nil
}
func GetBatchBy[T any, K comparable](name string, ct context.Context, key []K, timeout time.Duration, params ...any) (r []T, err error) {
ct = context.WithValue(ct, "getCache", name)
ca, err := getMap[K, T](name)
if err != nil {
return r, err
}
vv, err := ca.GetCacheBatch(ct, key, timeout, params...)
if err != nil {
return r, err
}
r = vv
return
}
func GetBatchByToMap[T any, K comparable](name string, ct context.Context, key []K, timeout time.Duration, params ...any) (r map[K]T, err error) {
ct = context.WithValue(ct, "getCache", name)
ca, err := getMap[K, T](name)
if err != nil {
return r, err
}
vv, err := ca.GetBatchToMap(ct, key, timeout, params...)
if err != nil {
return r, err
}
r = vv
return
}
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] {
inc := helper.ParseArgs((*cache.IncreaseUpdate[K, V])(nil), args...)
m := cache.NewMapCache[K, V](data, fn, batchFn, inc, buildLockFn[K](args...), args...)
name, f := parseArgs(args...)
if name != "" {
PushMangerMap(name, m)
}
PushOrSetFlush(mockmap.Item[string, Fn]{
Name: name,
Value: m.Flush,
})
PushOrSetClearExpired(mockmap.Item[string, Fn]{
Name: name,
Value: m.ClearExpired,
})
if f != nil && name != "" {
SetExpireTime(any(data).(cache.SetTime), name, 0, f)
}
return m
}
func NewMemoryMapCache[K comparable, V any](batchFn cache.MapBatchFn[K, V],
fn cache.MapSingleFn[K, V], expireTime time.Duration, args ...any) *cache.MapCache[K, V] {
c := NewMapCache[K, V](cache.NewMemoryMapCache[K, V](func() time.Duration {
return expireTime
}), batchFn, fn, args...)
return c
}
func GetMapCache[K comparable, V any](name string) (*cache.MapCache[K, V], bool) {
vv, err := getMap[K, V](name)
return vv, err == nil
}
func DelMapCacheVal[T any](name string, keys ...T) {
v, ok := mapDelFuncs.Load(name)
if !ok || len(keys) < 1 {
return
}
v(keys)
}

57
cache/cachemanager/pagination.go vendored Normal file
View File

@ -0,0 +1,57 @@
package cachemanager
import (
"context"
"errors"
"github.com/fthvgb1/wp-go/cache"
"github.com/fthvgb1/wp-go/cache/reload"
"github.com/fthvgb1/wp-go/helper"
str "github.com/fthvgb1/wp-go/helper/strings"
"time"
)
func NewPaginationCache[K comparable, V any](m *cache.MapCache[string, helper.PaginationData[V]], maxNum int,
dbFn cache.DbFn[K, V], localFn cache.LocalFn[K, V], dbKeyFn, localKeyFn func(K, ...any) string, fetchNum int, name string, a ...any) *cache.Pagination[K, V] {
fn := helper.ParseArgs([]func() int(nil), a...)
var ma, fet func() int
if len(fn) > 0 {
ma = fn[0]
if len(fn) > 1 {
fet = fn[1]
}
}
if ma == nil {
ma = reload.BuildFnVal(str.Join("paginationCache-", name, "-maxNum"), maxNum, nil)
}
if fet == nil {
fet = reload.BuildFnVal(str.Join("paginationCache-", name, "-fetchNum"), fetchNum, nil)
}
p := cache.NewPagination(m, ma, dbFn, localFn, dbKeyFn, localKeyFn, fet, name)
mapCache.Store(name, p)
return p
}
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
}

83
cache/cachemanager/varcache.go vendored Normal file
View File

@ -0,0 +1,83 @@
package cachemanager
import (
"context"
"errors"
"github.com/fthvgb1/wp-go/cache"
"github.com/fthvgb1/wp-go/helper"
"github.com/fthvgb1/wp-go/helper/slice/mockmap"
str "github.com/fthvgb1/wp-go/helper/strings"
"github.com/fthvgb1/wp-go/safety"
"time"
)
var varCache = safety.NewMap[string, any]()
func SetVarCache[T any](name string, v *cache.VarCache[T]) error {
vv, ok := varCache.Load(name)
if !ok {
varCache.Store(name, v)
return nil
}
_, ok = vv.(*cache.VarCache[T])
if ok {
varCache.Store(name, v)
return nil
}
return errors.New(str.Join("cache ", name, " type err"))
}
func NewVarCache[T any](c cache.AnyCache[T], fn func(context.Context, ...any) (T, error), a ...any) *cache.VarCache[T] {
inc := helper.ParseArgs((*cache.IncreaseUpdateVar[T])(nil), a...)
ref := helper.ParseArgs(cache.RefreshVar[T](nil), a...)
v := cache.NewVarCache(c, fn, inc, ref, a...)
name, _ := parseArgs(a...)
if name != "" {
varCache.Store(name, v)
}
PushOrSetFlush(mockmap.Item[string, Fn]{
Name: name,
Value: v.Flush,
})
cc, ok := any(c).(clearExpired)
if ok {
PushOrSetClearExpired(mockmap.Item[string, Fn]{
Name: name,
Value: cc.ClearExpired,
})
}
return v
}
func GetVarVal[T any](name string, ctx context.Context, duration time.Duration, a ...any) (r T, err error) {
ctx = context.WithValue(ctx, "getCache", name)
ca, ok := GetVarCache[T](name)
if !ok {
err = errors.New(str.Join("cache ", name, " is not exist"))
return
}
v, err := ca.GetCache(ctx, duration, a...)
if err != nil {
return
}
r = v
return
}
func NewVarMemoryCache[T any](fn func(context.Context, ...any) (T, error), expired time.Duration, a ...any) *cache.VarCache[T] {
c := cache.NewVarMemoryCache[T](nil)
name, e := parseArgs(a...)
SetExpireTime(c, name, expired, e)
v := NewVarCache[T](c, fn, a...)
return v
}
func GetVarCache[T any](name string) (*cache.VarCache[T], bool) {
v, ok := varCache.Load(name)
if !ok {
return nil, false
}
vv, ok := v.(*cache.VarCache[T])
return vv, ok
}

78
cache/locks.go vendored Normal file
View File

@ -0,0 +1,78 @@
package cache
import (
"context"
"github.com/fthvgb1/wp-go/safety"
"sync"
"sync/atomic"
)
type Locks[K comparable] struct {
numFn func() int
locks []*sync.Mutex
m *safety.Map[K, *sync.Mutex]
counter *int64
}
func (l *Locks[K]) Flush(_ context.Context) {
l.m.Flush()
atomic.StoreInt64(l.counter, 0)
}
func (l *Locks[K]) SetNumFn(numFn func() int) {
l.numFn = numFn
}
func NewLocks[K comparable](num func() int) *Locks[K] {
var i int64
return &Locks[K]{numFn: num, m: safety.NewMap[K, *sync.Mutex](), counter: &i}
}
func (l *Locks[K]) SetLockNum(num int) {
if num > 0 {
l.locks = make([]*sync.Mutex, num)
for i := 0; i < num; i++ {
l.locks[i] = &sync.Mutex{}
}
}
}
func (l *Locks[K]) GetLock(ctx context.Context, gMut *sync.Mutex, keys ...K) *sync.Mutex {
k := keys[0]
lo, ok := l.m.Load(k)
if ok {
return lo
}
num := l.numFn()
if num == 1 {
return gMut
}
gMut.Lock()
defer gMut.Unlock()
lo, ok = l.m.Load(k)
if ok {
return lo
}
if num <= 0 {
lo = &sync.Mutex{}
l.m.Store(k, lo)
return lo
}
if len(l.locks) == 0 {
l.SetLockNum(num)
}
counter := int(atomic.LoadInt64(l.counter))
if counter > len(l.locks)-1 {
atomic.StoreInt64(l.counter, 0)
counter = 0
}
lo = l.locks[counter]
l.m.Store(k, lo)
atomic.AddInt64(l.counter, 1)
if len(l.locks) < num {
for i := 0; i < num-len(l.locks); i++ {
l.locks = append(l.locks, &sync.Mutex{})
}
}
return lo
}

612
cache/map.go vendored
View File

@ -4,54 +4,200 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"github.com/fthvgb1/wp-go/helper/slice" "github.com/fthvgb1/wp-go/cache/reload"
"github.com/fthvgb1/wp-go/helper"
"github.com/fthvgb1/wp-go/helper/maps"
"sync" "sync"
"time" "time"
) )
type MapCache[K comparable, V any] struct { type MapCache[K comparable, V any] struct {
data Cache[K, V] Cache[K, V]
mux sync.Mutex mux *sync.Mutex
cacheFunc func(...any) (V, error) muFn func(ctx context.Context, gMut *sync.Mutex, k ...K) *sync.Mutex
batchCacheFn func(...any) (map[K]V, error) cacheFunc MapSingleFn[K, V]
expireTime time.Duration batchCacheFn MapBatchFn[K, V]
getCacheBatch func(c context.Context, key []K, timeout time.Duration, params ...any) ([]V, error)
getCacheBatchToMap func(c context.Context, key []K, timeout time.Duration, params ...any) (map[K]V, error)
increaseUpdate *IncreaseUpdate[K, V]
refresh Refresh[K, V]
gets func(ctx context.Context, key K) (V, bool)
sets func(ctx context.Context, key K, val V)
getExpireTimes func(ctx context.Context) time.Duration
ttl func(ctx context.Context, key K) time.Duration
flush func(ctx context.Context)
del func(ctx context.Context, key ...K)
clearExpired func(ctx context.Context)
} }
func (m *MapCache[K, V]) SetCacheFunc(fn func(...any) (V, error)) { func (m *MapCache[K, V]) Get(ctx context.Context, key K) (V, bool) {
return m.gets(ctx, key)
}
func (m *MapCache[K, V]) Set(ctx context.Context, key K, val V) {
m.sets(ctx, key, val)
}
func (m *MapCache[K, V]) Ttl(ctx context.Context, key K) time.Duration {
return m.ttl(ctx, key)
}
func (m *MapCache[K, V]) GetExpireTime(ctx context.Context) time.Duration {
return m.getExpireTimes(ctx)
}
func (m *MapCache[K, V]) Del(ctx context.Context, key ...K) {
m.del(ctx, key...)
}
func (m *MapCache[K, V]) ClearExpired(ctx context.Context) {
m.clearExpired(ctx)
}
type IncreaseUpdate[K comparable, V any] struct {
CycleTime func() time.Duration
Fn IncreaseFn[K, V]
}
func NewIncreaseUpdate[K comparable, V any](name string, fn IncreaseFn[K, V], cycleTime time.Duration, tFn func() time.Duration) *IncreaseUpdate[K, V] {
tFn = reload.BuildFnVal(name, cycleTime, tFn)
return &IncreaseUpdate[K, V]{CycleTime: tFn, Fn: fn}
}
type MapSingleFn[K, V any] func(context.Context, K, ...any) (V, error)
type MapBatchFn[K comparable, V any] func(context.Context, []K, ...any) (map[K]V, error)
type IncreaseFn[K comparable, V any] func(c context.Context, currentData V, k K, t time.Time, a ...any) (data V, save bool, refresh bool, err error)
func NewMapCache[K comparable, V any](ca Cache[K, V], cacheFunc MapSingleFn[K, V], batchCacheFn MapBatchFn[K, V], inc *IncreaseUpdate[K, V], lockFn LockFn[K], a ...any) *MapCache[K, V] {
r := &MapCache[K, V]{
Cache: ca,
mux: &sync.Mutex{},
cacheFunc: cacheFunc,
batchCacheFn: batchCacheFn,
increaseUpdate: inc,
muFn: lockFn,
}
if cacheFunc == nil && batchCacheFn != nil {
r.setDefaultCacheFn(batchCacheFn)
} else if batchCacheFn == nil && cacheFunc != nil {
r.SetDefaultBatchFunc(cacheFunc)
}
ex, ok := any(ca).(Expend[K, V])
if !ok {
r.getCacheBatch = r.getCacheBatchs
r.getCacheBatchToMap = r.getBatchToMapes
} else {
r.getCacheBatch = r.getBatches(ex)
r.getCacheBatchToMap = r.getBatchToMap(ex)
}
re, ok := any(ca).(Refresh[K, V])
if ok {
r.refresh = re
}
initCache(r, a...)
return r
}
func initCache[K comparable, V any](r *MapCache[K, V], a ...any) {
gets := helper.ParseArgs[func(Cache[K, V], context.Context, K) (V, bool)](nil, a...)
if gets == nil {
r.gets = r.Cache.Get
} else {
r.gets = func(ctx context.Context, key K) (V, bool) {
return gets(r.Cache, ctx, key)
}
}
sets := helper.ParseArgs[func(Cache[K, V], context.Context, K, V)](nil, a...)
if sets == nil {
r.sets = r.Cache.Set
} else {
r.sets = func(ctx context.Context, key K, val V) {
sets(r.Cache, ctx, key, val)
}
}
getExpireTimes := helper.ParseArgs[func(Cache[K, V], context.Context) time.Duration](nil, a...)
if getExpireTimes == nil {
r.getExpireTimes = r.Cache.GetExpireTime
} else {
r.getExpireTimes = func(ctx context.Context) time.Duration {
return getExpireTimes(r.Cache, ctx)
}
}
ttl := helper.ParseArgs[func(Cache[K, V], context.Context, K) time.Duration](nil, a...)
if ttl == nil {
r.ttl = r.Cache.Ttl
} else {
r.ttl = func(ctx context.Context, k K) time.Duration {
return ttl(r.Cache, ctx, k)
}
}
del := helper.ParseArgs[func(Cache[K, V], context.Context, ...K)](nil, a...)
if del == nil {
r.del = r.Cache.Del
} else {
r.del = func(ctx context.Context, key ...K) {
del(r.Cache, ctx, key...)
}
}
flushAndClearExpired := helper.ParseArgs[[]func(Cache[K, V], context.Context)](nil, a...)
if flushAndClearExpired == nil {
r.flush = r.Cache.Flush
r.clearExpired = r.Cache.ClearExpired
} else {
r.flush = func(ctx context.Context) {
flushAndClearExpired[0](r.Cache, ctx)
}
if len(flushAndClearExpired) > 1 {
r.clearExpired = func(ctx context.Context) {
flushAndClearExpired[1](r.Cache, ctx)
}
} else {
r.clearExpired = r.Cache.ClearExpired
}
}
}
func (m *MapCache[K, V]) SetDefaultBatchFunc(fn MapSingleFn[K, V]) {
m.batchCacheFn = func(ctx context.Context, ids []K, a ...any) (map[K]V, error) {
var err error
rr := make(map[K]V)
for _, id := range ids {
v, er := fn(ctx, id, a...)
if er != nil {
err = errors.Join(er)
continue
}
rr[id] = v
}
return rr, err
}
}
func (m *MapCache[K, V]) SetCacheFunc(fn MapSingleFn[K, V]) {
m.cacheFunc = fn m.cacheFunc = fn
} }
func (m *MapCache[K, V]) Ttl(ctx context.Context, k K) time.Duration {
return m.data.Ttl(ctx, k, m.expireTime)
}
func (m *MapCache[K, V]) GetLastSetTime(ctx context.Context, k K) (t time.Time) { func (m *MapCache[K, V]) GetLastSetTime(ctx context.Context, k K) (t time.Time) {
tt := m.data.Ttl(ctx, k, m.expireTime) tt := m.Ttl(ctx, k)
if tt <= 0 { if tt <= 0 {
return return
} }
return time.Now().Add(m.data.Ttl(ctx, k, m.expireTime)).Add(-m.expireTime) return time.Now().Add(m.Ttl(ctx, k)).Add(-m.GetExpireTime(ctx))
} }
func (m *MapCache[K, V]) SetCacheBatchFn(fn func(...any) (map[K]V, error)) { func (m *MapCache[K, V]) SetCacheBatchFn(fn MapBatchFn[K, V]) {
m.batchCacheFn = fn m.batchCacheFn = fn
if m.cacheFunc == nil { if m.cacheFunc == nil {
m.setCacheFn(fn) m.setDefaultCacheFn(fn)
} }
} }
func (m *MapCache[K, V]) setCacheFn(fn func(...any) (map[K]V, error)) { func (m *MapCache[K, V]) setDefaultCacheFn(fn MapBatchFn[K, V]) {
m.cacheFunc = func(a ...any) (V, error) { m.cacheFunc = func(ctx context.Context, k K, a ...any) (V, error) {
var err error var err error
var r map[K]V var r map[K]V
var k K r, err = fn(ctx, []K{k}, a...)
ctx, ok := a[0].(context.Context)
if ok {
k, ok = a[1].(K)
if ok {
r, err = fn(ctx, []K{k})
}
}
if err != nil { if err != nil {
var rr V var rr V
@ -61,114 +207,145 @@ func (m *MapCache[K, V]) setCacheFn(fn func(...any) (map[K]V, error)) {
} }
} }
func NewMapCacheByFn[K comparable, V any](cacheType Cache[K, V], fn func(...any) (V, error), expireTime time.Duration) *MapCache[K, V] {
return &MapCache[K, V]{
mux: sync.Mutex{},
cacheFunc: fn,
expireTime: expireTime,
data: cacheType,
}
}
func NewMapCacheByBatchFn[K comparable, V any](cacheType Cache[K, V], fn func(...any) (map[K]V, error), expireTime time.Duration) *MapCache[K, V] {
r := &MapCache[K, V]{
mux: sync.Mutex{},
batchCacheFn: fn,
expireTime: expireTime,
data: cacheType,
}
r.setCacheFn(fn)
return r
}
func (m *MapCache[K, V]) Flush(ctx context.Context) { func (m *MapCache[K, V]) Flush(ctx context.Context) {
m.mux.Lock() m.mux.Lock()
defer m.mux.Unlock() defer m.mux.Unlock()
m.data.Flush(ctx) m.flush(ctx)
} }
func (m *MapCache[K, V]) Get(ctx context.Context, k K) (V, bool) { func (m *MapCache[K, V]) increaseUpdates(c context.Context, timeout time.Duration, data V, key K, params ...any) (V, error) {
return m.data.Get(ctx, k) var err error
} nowTime := time.Now()
if nowTime.Sub(m.GetLastSetTime(c, key)) < m.increaseUpdate.CycleTime() {
func (m *MapCache[K, V]) Set(ctx context.Context, k K, v V) { return data, err
m.data.Set(ctx, k, v, m.expireTime) }
fn := func() {
l := m.muFn(c, m.mux, key)
l.Lock()
defer l.Unlock()
if nowTime.Sub(m.GetLastSetTime(c, key)) < m.increaseUpdate.CycleTime() {
return
}
dat, save, refresh, er := m.increaseUpdate.Fn(c, data, key, m.GetLastSetTime(c, key), params...)
if er != nil {
err = er
return
}
if refresh {
m.refresh.Refresh(c, key, params...)
}
if save {
m.Set(c, key, dat)
data = dat
}
}
if timeout > 0 {
er := helper.RunFnWithTimeout(c, timeout, fn)
if err == nil && er != nil {
return data, fmt.Errorf("increateUpdate cache %v err:[%s]", key, er)
}
} else {
fn()
}
return data, err
} }
func (m *MapCache[K, V]) GetCache(c context.Context, key K, timeout time.Duration, params ...any) (V, error) { func (m *MapCache[K, V]) GetCache(c context.Context, key K, timeout time.Duration, params ...any) (V, error) {
data, ok := m.data.Get(c, key) data, ok := m.Get(c, key)
var err error var err error
if !ok || m.data.Ttl(c, key, m.expireTime) <= 0 { if ok {
ver := m.data.Ver(c, key) if m.increaseUpdate == nil || m.refresh == nil {
call := func() { return data, err
m.mux.Lock()
defer m.mux.Unlock()
if m.data.Ver(c, key) > ver {
data, _ = m.data.Get(c, key)
return
}
data, err = m.cacheFunc(params...)
if err != nil {
return
}
m.Set(c, key, data)
} }
if timeout > 0 { return m.increaseUpdates(c, timeout, data, key, params...)
ctx, cancel := context.WithTimeout(c, timeout) }
defer cancel() call := func() {
done := make(chan struct{}, 1) l := m.muFn(c, m.mux, key)
go func() { l.Lock()
call() defer l.Unlock()
done <- struct{}{} if data, ok = m.Get(c, key); ok {
}() return
select {
case <-ctx.Done():
err = errors.New(fmt.Sprintf("get cache %v %s", key, ctx.Err().Error()))
case <-done:
}
} else {
call()
} }
data, err = m.cacheFunc(c, key, params...)
if err != nil {
return
}
m.Set(c, key, data)
}
if timeout > 0 {
er := helper.RunFnWithTimeout(c, timeout, call, fmt.Sprintf("get cache %v ", key))
if err == nil && er != nil {
err = er
}
} else {
call()
} }
return data, err return data, err
} }
func (m *MapCache[K, V]) GetCacheBatch(c context.Context, key []K, timeout time.Duration, params ...any) ([]V, error) { func (m *MapCache[K, V]) GetCacheBatch(c context.Context, key []K, timeout time.Duration, params ...any) ([]V, error) {
var res []V return m.getCacheBatch(c, key, timeout, params...)
ver := 0 }
needFlush := slice.FilterAndMap(key, func(k K) (r K, ok bool) {
if _, ok := m.data.Get(c, k); !ok { func (m *MapCache[K, V]) GetBatchToMap(c context.Context, key []K, timeout time.Duration, params ...any) (map[K]V, error) {
return k, true return m.getCacheBatchToMap(c, key, timeout, params...)
} }
ver += m.data.Ver(c, k) func (m *MapCache[K, V]) getBatchToMap(e Expend[K, V]) func(c context.Context, key []K, timeout time.Duration, params ...any) (map[K]V, error) {
return return func(ctx context.Context, key []K, timeout time.Duration, params ...any) (map[K]V, error) {
}) var res map[K]V
var err error
mm, err := e.Gets(ctx, key)
if err != nil || len(key) == len(mm) {
return mm, err
}
var needIndex = make(map[K]int)
res = mm
var flushKeys []K
for i, k := range key {
_, ok := mm[k]
if !ok {
flushKeys = append(flushKeys, k)
needIndex[k] = i
}
}
var err error
if len(needFlush) > 0 {
call := func() { call := func() {
m.mux.Lock() m.mux.Lock()
defer m.mux.Unlock() defer m.mux.Unlock()
mmm, er := e.Gets(ctx, maps.FilterToSlice(needIndex, func(k K, v int) (K, bool) {
vers := slice.Reduce(needFlush, func(t K, r int) int { return k, true
return r + m.data.Ver(c, t) }))
}, 0) if er != nil {
if vers > ver {
return
}
r, er := m.batchCacheFn(params...)
if err != nil {
err = er err = er
return return
} }
for k, v := range r { for k, v := range mmm {
m.Set(c, k, v) res[k] = v
delete(needIndex, k)
}
if len(needIndex) < 1 {
return
}
r, er := m.batchCacheFn(ctx, maps.FilterToSlice(needIndex, func(k K, v int) (K, bool) {
return k, true
}), params...)
if er != nil {
err = er
return
}
e.Sets(ctx, r)
for k := range needIndex {
v, ok := r[k]
if ok {
res[k] = v
}
} }
} }
if timeout > 0 { if timeout > 0 {
ctx, cancel := context.WithTimeout(c, timeout) ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel() defer cancel()
done := make(chan struct{}, 1) done := make(chan struct{}, 1)
go func() { go func() {
@ -178,20 +355,227 @@ func (m *MapCache[K, V]) GetCacheBatch(c context.Context, key []K, timeout time.
select { select {
case <-ctx.Done(): case <-ctx.Done():
err = errors.New(fmt.Sprintf("get cache %v %s", key, ctx.Err().Error())) err = errors.New(fmt.Sprintf("get cache %v %s", key, ctx.Err().Error()))
return nil, err
case <-done: case <-done:
} }
} else { } else {
call() call()
} }
return res, err
} }
res = slice.FilterAndMap(key, func(k K) (V, bool) { }
return m.data.Get(c, k) func (m *MapCache[K, V]) getBatchToMapes(c context.Context, key []K, timeout time.Duration, params ...any) (r map[K]V, err error) {
}) r = make(map[K]V)
var needIndex = make(map[K]int)
for i, k := range key {
v, ok := m.Get(c, k)
if !ok {
needIndex[k] = i
} else {
r[k] = v
}
}
if len(needIndex) < 1 {
return
}
call := func() {
l := m.muFn(c, m.mux, key...)
l.Lock()
defer l.Unlock()
needFlushs := maps.FilterToSlice(needIndex, func(k K, v int) (K, bool) {
vv, ok := m.Get(c, k)
if ok {
r[k] = vv
delete(needIndex, k)
return k, false
}
return k, true
})
if len(needFlushs) < 1 {
return
}
rr, er := m.batchCacheFn(c, needFlushs, params...)
if er != nil {
err = er
return
}
for k := range needIndex {
v, ok := rr[k]
if ok {
r[k] = v
}
m.Set(c, k, v)
}
}
if timeout > 0 {
ctx, cancel := context.WithTimeout(c, timeout)
defer cancel()
done := make(chan struct{}, 1)
go func() {
call()
done <- struct{}{}
}()
select {
case <-ctx.Done():
err = errors.New(fmt.Sprintf("get cache %v %s", key, ctx.Err().Error()))
return nil, err
case <-done:
}
} else {
call()
}
return
}
func (m *MapCache[K, V]) getCacheBatchs(c context.Context, key []K, timeout time.Duration, params ...any) ([]V, error) {
var res = make([]V, 0, len(key))
var needIndex = make(map[K]int)
for i, k := range key {
v, ok := m.Get(c, k)
if !ok {
needIndex[k] = i
}
res = append(res, v)
}
if len(needIndex) < 1 {
return res, nil
}
var err error
call := func() {
l := m.muFn(c, m.mux, key...)
l.Lock()
defer l.Unlock()
needFlushs := maps.FilterToSlice(needIndex, func(k K, v int) (K, bool) {
vv, ok := m.Get(c, k)
if ok {
res[needIndex[k]] = vv
delete(needIndex, k)
return k, false
}
return k, true
})
if len(needFlushs) < 1 {
return
}
r, er := m.batchCacheFn(c, needFlushs, params...)
if er != nil {
err = er
return
}
for k, i := range needIndex {
v, ok := r[k]
if ok {
res[i] = v
m.Set(c, k, v)
}
}
}
if timeout > 0 {
ctx, cancel := context.WithTimeout(c, timeout)
defer cancel()
done := make(chan struct{}, 1)
go func() {
call()
done <- struct{}{}
}()
select {
case <-ctx.Done():
err = errors.New(fmt.Sprintf("get cache %v %s", key, ctx.Err().Error()))
return nil, err
case <-done:
}
} else {
call()
}
return res, err return res, err
} }
func (m *MapCache[K, V]) ClearExpired(ctx context.Context) { func (m *MapCache[K, V]) getBatches(e Expend[K, V]) func(ctx context.Context, key []K, timeout time.Duration, params ...any) ([]V, error) {
m.mux.Lock() cc := e
defer m.mux.Unlock() return func(ctx context.Context, key []K, timeout time.Duration, params ...any) ([]V, error) {
m.data.ClearExpired(ctx, m.expireTime) var res = make([]V, 0, len(key))
var needIndex = make(map[K]int)
var err error
mm, err := cc.Gets(ctx, key)
if err != nil {
return nil, err
}
var flushKeys []K
for i, k := range key {
v, ok := mm[k]
if !ok {
flushKeys = append(flushKeys, k)
needIndex[k] = i
var vv V
v = vv
}
res = append(res, v)
}
if len(needIndex) < 1 {
return res, nil
}
call := func() {
m.mux.Lock()
defer m.mux.Unlock()
mmm, er := cc.Gets(ctx, maps.FilterToSlice(needIndex, func(k K, v int) (K, bool) {
return k, true
}))
if er != nil {
err = er
return
}
for k, v := range mmm {
res[needIndex[k]] = v
delete(needIndex, k)
}
if len(needIndex) < 1 {
return
}
r, er := m.batchCacheFn(ctx, maps.FilterToSlice(needIndex, func(k K, v int) (K, bool) {
return k, true
}), params...)
if er != nil {
err = er
return
}
cc.Sets(ctx, r)
for k, i := range needIndex {
v, ok := r[k]
if ok {
res[i] = v
}
}
}
if timeout > 0 {
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
done := make(chan struct{}, 1)
go func() {
call()
done <- struct{}{}
}()
select {
case <-ctx.Done():
err = errors.New(fmt.Sprintf("get cache %v %s", key, ctx.Err().Error()))
return nil, err
case <-done:
}
} else {
call()
}
return res, err
}
} }

39
cache/map_test.go vendored
View File

@ -12,27 +12,22 @@ import (
) )
var ca MapCache[string, string] var ca MapCache[string, string]
var fn func(a ...any) (string, error) var fn MapSingleFn[string, string]
var batchFn func(a ...any) (map[string]string, error) var batchFn MapBatchFn[string, string]
var ct context.Context var ct context.Context
func init() { func init() {
fn = func(a ...any) (string, error) { fn = func(ctx context.Context, aa string, a ...any) (string, error) {
aa := a[1].(string)
return strings.Repeat(aa, 2), nil return strings.Repeat(aa, 2), nil
} }
ct = context.Background() ct = context.Background()
batchFn = func(a ...any) (map[string]string, error) { batchFn = func(ctx context.Context, arr []string, a ...any) (map[string]string, error) {
fmt.Println(a) fmt.Println(a)
arr := a[1].([]string) return slice.FilterAndToMap(arr, func(t string, _ int) (string, string, bool) {
return slice.FilterAndToMap(arr, func(t string) (string, string, bool) {
return t, strings.Repeat(t, 2), true return t, strings.Repeat(t, 2), true
}), nil }), nil
} }
ca = *NewMemoryMapCacheByFn[string, string](fn, time.Second*2)
ca.SetCacheBatchFn(batchFn)
_, _ = ca.GetCache(ct, "aa", time.Second, ct, "aa")
_, _ = ca.GetCache(ct, "bb", time.Second, ct, "bb")
} }
func TestMapCache_ClearExpired(t *testing.T) { func TestMapCache_ClearExpired(t *testing.T) {
type args struct { type args struct {
@ -72,7 +67,9 @@ func TestMapCache_Flush(t *testing.T) {
m MapCache[K, V] m MapCache[K, V]
args args args args
} }
ca := *NewMemoryMapCacheByFn[string, string](fn, time.Second) ca := *NewMapCache[string, string](NewMemoryMapCache[string, string](func() time.Duration {
return time.Second
}), fn, nil, nil, nil)
_, _ = ca.GetCache(ct, "aa", time.Second, ct, "aa") _, _ = ca.GetCache(ct, "aa", time.Second, ct, "aa")
tests := []testCase[string, string]{ tests := []testCase[string, string]{
{ {
@ -293,7 +290,7 @@ func TestMapCache_Set(t *testing.T) {
func TestMapCache_SetCacheBatchFn(t *testing.T) { func TestMapCache_SetCacheBatchFn(t *testing.T) {
type args[K comparable, V any] struct { type args[K comparable, V any] struct {
fn func(...any) (map[K]V, error) fn MapBatchFn[K, V]
} }
type testCase[K comparable, V any] struct { type testCase[K comparable, V any] struct {
name string name string
@ -315,19 +312,19 @@ func TestMapCache_SetCacheBatchFn(t *testing.T) {
} }
func TestMapCache_SetCacheFunc(t *testing.T) { func TestMapCache_SetCacheFunc(t *testing.T) {
type args[V any] struct { type args[K comparable, V any] struct {
fn func(...any) (V, error) fn MapSingleFn[K, V]
} }
type testCase[K comparable, V any] struct { type testCase[K comparable, V any] struct {
name string name string
m MapCache[K, V] m MapCache[K, V]
args args[V] args args[K, V]
} }
tests := []testCase[string, string]{ tests := []testCase[string, string]{
{ {
name: "t1", name: "t1",
m: ca, m: ca,
args: args[string]{fn: fn}, args: args[string, string]{fn: fn},
}, },
} }
for _, tt := range tests { for _, tt := range tests {
@ -355,12 +352,12 @@ func TestMapCache_Ttl(t *testing.T) {
name: "t1", name: "t1",
m: ca, m: ca,
args: args[string]{ct, "aa"}, args: args[string]{ct, "aa"},
want: ca.expireTime - tx.Sub(txx), want: ca.GetExpireTime(ct) - tx.Sub(txx),
}, },
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
fmt.Printf("过期时间=%v \nttl=%v \n当前时间 =%v\n最后设置时间=%v\n当时时间-最后设置时间=%v ", ca.expireTime, ca.Ttl(ct, "aa"), tx, txx, tx.Sub(txx)) fmt.Printf("过期时间=%v \nttl=%v \n当前时间 =%v\n最后设置时间=%v\n当时时间-最后设置时间=%v ", ca.GetExpireTime(ct), ca.Ttl(ct, "aa"), tx, txx, tx.Sub(txx))
if got := tt.m.Ttl(tt.args.ct, tt.args.k); got != tt.want { if got := tt.m.Ttl(tt.args.ct, tt.args.k); got != tt.want {
t.Errorf("Ttl() = %v, want %v", got, tt.want) t.Errorf("Ttl() = %v, want %v", got, tt.want)
} }
@ -370,7 +367,7 @@ func TestMapCache_Ttl(t *testing.T) {
func TestMapCache_setCacheFn(t *testing.T) { func TestMapCache_setCacheFn(t *testing.T) {
type args[K comparable, V any] struct { type args[K comparable, V any] struct {
fn func(...any) (map[K]V, error) fn MapBatchFn[K, V]
} }
type testCase[K comparable, V any] struct { type testCase[K comparable, V any] struct {
name string name string
@ -387,7 +384,7 @@ func TestMapCache_setCacheFn(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
ca.cacheFunc = nil ca.cacheFunc = nil
tt.m.setCacheFn(tt.args.fn) tt.m.setDefaultCacheFn(tt.args.fn)
fmt.Println(ca.GetCache(ct, "xx", time.Second, ct, "xx")) fmt.Println(ca.GetCache(ct, "xx", time.Second, ct, "xx"))
}) })
} }

View File

@ -2,37 +2,22 @@ package cache
import ( import (
"context" "context"
"github.com/fthvgb1/wp-go/helper"
"github.com/fthvgb1/wp-go/safety" "github.com/fthvgb1/wp-go/safety"
"sync"
"time" "time"
) )
type MemoryMapCache[K comparable, V any] struct { type MemoryMapCache[K comparable, V any] struct {
*safety.Map[K, mapVal[V]] *safety.Map[K, mapVal[V]]
expireTime func() time.Duration
} }
func NewMemoryMapCacheByFn[K comparable, V any](fn func(...any) (V, error), expireTime time.Duration) *MapCache[K, V] { func NewMemoryMapCache[K comparable, V any](expireTime func() time.Duration) *MemoryMapCache[K, V] {
return &MapCache[K, V]{ return &MemoryMapCache[K, V]{
data: NewMemoryMapCache[K, V](), Map: safety.NewMap[K, mapVal[V]](),
cacheFunc: fn,
expireTime: expireTime, expireTime: expireTime,
mux: sync.Mutex{},
} }
} }
func NewMemoryMapCacheByBatchFn[K comparable, V any](fn func(...any) (map[K]V, error), expireTime time.Duration) *MapCache[K, V] {
r := &MapCache[K, V]{
data: NewMemoryMapCache[K, V](),
batchCacheFn: fn,
expireTime: expireTime,
mux: sync.Mutex{},
}
r.setCacheFn(fn)
return r
}
func NewMemoryMapCache[K comparable, V any]() *MemoryMapCache[K, V] {
return &MemoryMapCache[K, V]{Map: safety.NewMap[K, mapVal[V]]()}
}
type mapVal[T any] struct { type mapVal[T any] struct {
setTime time.Time setTime time.Time
@ -40,15 +25,28 @@ type mapVal[T any] struct {
data T data T
} }
func (m *MemoryMapCache[K, V]) SetExpiredTime(f func() time.Duration) {
m.expireTime = f
}
func (m *MemoryMapCache[K, V]) GetExpireTime(_ context.Context) time.Duration {
return m.expireTime()
}
func (m *MemoryMapCache[K, V]) Get(_ context.Context, key K) (r V, ok bool) { func (m *MemoryMapCache[K, V]) Get(_ context.Context, key K) (r V, ok bool) {
v, ok := m.Load(key) v, ok := m.Load(key)
if ok { if !ok {
return v.data, true return
}
r = v.data
t := m.expireTime() - time.Now().Sub(v.setTime)
if t <= 0 {
ok = false
} }
return return
} }
func (m *MemoryMapCache[K, V]) Set(_ context.Context, key K, val V, _ time.Duration) { func (m *MemoryMapCache[K, V]) Set(_ context.Context, key K, val V) {
v, ok := m.Load(key) v, ok := m.Load(key)
t := time.Now() t := time.Now()
if ok { if ok {
@ -65,12 +63,12 @@ func (m *MemoryMapCache[K, V]) Set(_ context.Context, key K, val V, _ time.Durat
m.Store(key, v) m.Store(key, v)
} }
func (m *MemoryMapCache[K, V]) Ttl(_ context.Context, key K, expire time.Duration) time.Duration { func (m *MemoryMapCache[K, V]) Ttl(_ context.Context, key K) time.Duration {
v, ok := m.Load(key) v, ok := m.Load(key)
if !ok { if !ok {
return time.Duration(-1) return time.Duration(-1)
} }
return expire - time.Now().Sub(v.setTime) return m.expireTime() - time.Now().Sub(v.setTime)
} }
func (m *MemoryMapCache[K, V]) Ver(_ context.Context, key K) int { func (m *MemoryMapCache[K, V]) Ver(_ context.Context, key K) int {
@ -85,17 +83,28 @@ func (m *MemoryMapCache[K, V]) Flush(context.Context) {
m.Map.Flush() m.Map.Flush()
} }
func (m *MemoryMapCache[K, V]) Delete(_ context.Context, key K) { func (m *MemoryMapCache[K, V]) Del(_ context.Context, keys ...K) {
m.Map.Delete(key) for _, key := range keys {
m.Map.Delete(key)
}
} }
func (m *MemoryMapCache[K, V]) ClearExpired(_ context.Context, expire time.Duration) { func (m *MemoryMapCache[K, V]) ClearExpired(_ context.Context) {
now := time.Duration(time.Now().UnixNano()) now := time.Duration(time.Now().UnixNano())
m.Range(func(k K, v mapVal[V]) bool { m.Range(func(k K, v mapVal[V]) bool {
if now > time.Duration(v.setTime.UnixNano())+expire { if now > time.Duration(v.setTime.UnixNano())+m.expireTime() {
m.Map.Delete(k) m.Map.Delete(k)
} }
return true return true
}) })
} }
func (m *MemoryMapCache[K, V]) Refresh(_ context.Context, k K, a ...any) {
v, ok := m.Load(k)
if !ok {
return
}
t := helper.ParseArgs(time.Now(), a...)
v.setTime = t
m.Store(k, v)
}

View File

@ -15,7 +15,7 @@ var ttt time.Time
func init() { func init() {
ctx = context.Background() ctx = context.Background()
mm = *NewMemoryMapCache[string, string]() mm = *NewMemoryMapCache[string, string](3 * time.Second)
ttt = time.Now() ttt = time.Now()
mm.Store("aa", mapVal[string]{ mm.Store("aa", mapVal[string]{
setTime: ttt, setTime: ttt,
@ -53,7 +53,7 @@ func TestMemoryMapCache_ClearExpired(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
fmt.Println(tt.m) fmt.Println(tt.m)
tt.m.ClearExpired(tt.args.in0, tt.args.expire) tt.m.ClearExpired(tt.args.in0)
time.Sleep(time.Second) time.Sleep(time.Second)
fmt.Println(tt.m) fmt.Println(tt.m)
}) })
@ -83,7 +83,7 @@ func TestMemoryMapCache_Delete(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
fmt.Println(mm.Get(ctx, "aa")) fmt.Println(mm.Get(ctx, "aa"))
tt.m.Delete(tt.args.in0, tt.args.key) tt.m.Del(tt.args.in0, tt.args.key)
fmt.Println(mm.Get(ctx, "aa")) fmt.Println(mm.Get(ctx, "aa"))
}) })
@ -111,7 +111,7 @@ func TestMemoryMapCache_Flush(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
tt.m.Flush(tt.args.in0) tt.m.Flush(tt.args.in0)
mm.Set(ctx, "aa", "xx", time.Second) mm.Set(ctx, "aa", "xx")
fmt.Println(mm.Get(ctx, "aa")) fmt.Println(mm.Get(ctx, "aa"))
}) })
} }
@ -180,7 +180,7 @@ func TestMemoryMapCache_Set(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
tt.m.Set(tt.args.in0, tt.args.key, tt.args.val, tt.args.in3) tt.m.Set(tt.args.in0, tt.args.key, tt.args.val)
fmt.Println(tt.m.Get(ctx, tt.args.key)) fmt.Println(tt.m.Get(ctx, tt.args.key))
}) })
} }
@ -209,7 +209,7 @@ func TestMemoryMapCache_Ttl(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
if got := tt.m.Ttl(tt.args.in0, tt.args.key, tt.args.expire); got != tt.want { if got := tt.m.Ttl(tt.args.in0, tt.args.key); got != tt.want {
t.Errorf("Ttl() = %v, want %v", got, tt.want) t.Errorf("Ttl() = %v, want %v", got, tt.want)
} }
}) })
@ -227,7 +227,7 @@ func TestMemoryMapCache_Ver(t *testing.T) {
args args[K] args args[K]
want int want int
} }
mm.Set(ctx, "aa", "ff", time.Second) mm.Set(ctx, "aa", "ff")
tests := []testCase[string, string]{ tests := []testCase[string, string]{
{ {
name: "t1", name: "t1",

186
cache/pagination.go vendored Normal file
View File

@ -0,0 +1,186 @@
package cache
import (
"context"
"errors"
"fmt"
"github.com/fthvgb1/wp-go/helper"
"github.com/fthvgb1/wp-go/helper/number"
str "github.com/fthvgb1/wp-go/helper/strings"
"github.com/fthvgb1/wp-go/safety"
"strings"
"time"
)
type Pagination[K comparable, V any] struct {
*MapCache[string, helper.PaginationData[V]]
maxNum func() int
isSwitch *safety.Map[K, bool]
dbFn func(ctx context.Context, k K, page, limit, totalRaw int, a ...any) ([]V, int, error)
localFn func(ctx context.Context, data []V, k K, page, limit int, a ...any) ([]V, int, error)
batchFetchNum func() int
localKeyFn func(K K, a ...any) string
dbKeyFn func(K K, a ...any) string
name string
}
var switchDb = errors.New("switch Db")
type DbFn[K comparable, V any] func(ctx context.Context, k K, page, limit, totalRaw int, a ...any) ([]V, int, error)
type LocalFn[K comparable, V any] func(ctx context.Context, data []V, k K, page, limit int, a ...any) ([]V, int, error)
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()
for _, v := range append([]any{k}, a...) {
s.Sprintf("%v|", v)
}
return strings.TrimRight(s.String(), "|")
}
}
if localKeyFn == nil {
localKeyFn = func(k K, a ...any) string {
return fmt.Sprintf("%v", k)
}
}
return &Pagination[K, V]{
MapCache: m,
maxNum: maxNum,
isSwitch: safety.NewMap[K, bool](),
dbFn: dbFn,
localFn: localFn,
batchFetchNum: batchFetchNum,
name: name,
dbKeyFn: dbKeyFn,
localKeyFn: localKeyFn,
}
}
func (p *Pagination[K, V]) Pagination(ctx context.Context, timeout time.Duration, k K, page, limit int, a ...any) ([]V, int, error) {
if is, _ := p.isSwitch.Load(k); is {
return p.paginationByDB(ctx, timeout, k, page, limit, 0, a...)
}
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...)
}
return nil, 0, err
}
return data, total, err
}
func (p *Pagination[K, V]) paginationByLocal(ctx context.Context, timeout time.Duration, k K, page, limit int, a ...any) ([]V, int, error) {
key := p.localKeyFn(k)
data, ok := p.Get(ctx, key)
if ok {
if p.increaseUpdate != nil && p.refresh != nil {
dat, err := p.increaseUpdates(ctx, timeout, data, key, a...)
if err != nil {
return nil, 0, err
}
if dat.TotalRaw >= p.maxNum() {
return nil, 0, switchDb
}
data = dat
}
return p.localFn(ctx, data.Data, k, page, limit, a...)
}
p.mux.Lock()
defer p.mux.Unlock()
data, ok = p.Get(ctx, key)
if ok {
return data.Data, data.TotalRaw, nil
}
batchNum := p.batchFetchNum()
da, totalRaw, err := p.fetchDb(ctx, timeout, k, 1, 0, 0, a...)
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() {
return nil, totalRaw, switchDb
}
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...)
}
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.dbGet(ctx, key)
if ok {
return data.Data, data.TotalRaw, nil
}
p.mux.Lock()
defer p.mux.Unlock()
data, ok = p.dbGet(ctx, key)
if ok {
return data.Data, data.TotalRaw, nil
}
dat, total, err := p.fetchDb(ctx, timeout, k, page, limit, totalRaw, a...)
if err != nil {
return nil, 0, err
}
data.Data, data.TotalRaw = dat, total
p.Set(ctx, key, data)
return data.Data, data.TotalRaw, err
}
func (p *Pagination[K, V]) fetchDb(ctx context.Context, timeout time.Duration, k K, page, limit, totalRaw int, a ...any) ([]V, int, error) {
var data helper.PaginationData[V]
var err error
fn := func() {
da, total, er := p.dbFn(ctx, k, page, limit, totalRaw, a...)
if er != nil {
err = er
return
}
data.Data = da
data.TotalRaw = total
}
if timeout > 0 {
er := helper.RunFnWithTimeout(ctx, timeout, fn, fmt.Sprintf("fetch %s-[%v]-page[%d]-limit[%d] from db fail", p.name, k, page, limit))
if err == nil && er != nil {
err = er
}
} else {
fn()
}
return data.Data, data.TotalRaw, err
}

544
cache/reload/reload.go vendored Normal file
View File

@ -0,0 +1,544 @@
package reload
import (
"github.com/fthvgb1/wp-go/helper"
"github.com/fthvgb1/wp-go/helper/slice"
str "github.com/fthvgb1/wp-go/helper/strings"
"github.com/fthvgb1/wp-go/safety"
"sync"
"sync/atomic"
)
type Queue struct {
Fn func()
Order float64
Name string
AutoExec bool
Once bool
}
var mut = &sync.Mutex{}
func GetGlobeMutex() *sync.Mutex {
return mut
}
var reloadQueues = safety.NewSlice[Queue]()
var reloadQueueHookFns = safety.NewVar[[]func(queue Queue) (Queue, bool)](nil)
var setFnVal = safety.NewMap[string, any]()
func DeleteReloadQueue(names ...string) {
hooks := reloadQueueHookFns.Load()
for _, name := range names {
hooks = append(hooks, func(queue Queue) (Queue, bool) {
if name != queue.Name {
return queue, true
}
return queue, false
})
}
reloadQueueHookFns.Store(hooks)
}
func HookReloadQueue(fn func(queue Queue) (Queue, bool)) {
a := reloadQueueHookFns.Load()
a = append(a, fn)
reloadQueueHookFns.Store(a)
}
func GetReloadFn(name string) func() {
hookQueue()
i, queue := slice.SearchFirst(reloadQueues.Load(), func(queue Queue) bool {
return queue.Name == name
})
if i > -1 && queue.Fn != nil {
return queue.Fn
}
return nil
}
func hookQueue() {
hooks := reloadQueueHookFns.Load()
queues := reloadQueues.Load()
length := len(queues)
for _, hook := range hooks {
queues = slice.FilterAndMap(queues, hook)
}
if len(queues) != length {
reloadQueues.Store(queues)
}
reloadQueueHookFns.Flush()
}
type SafetyVar[T, A any] struct {
Val *safety.Var[Val[T]]
Mutex sync.Mutex
}
type Val[T any] struct {
V T
Ok bool
}
type SafetyMap[K comparable, V, A any] struct {
Val *safety.Map[K, V]
Mutex sync.Mutex
}
var safetyMaps = safety.NewMap[string, any]()
var safetyMapLock = sync.Mutex{}
var deleteMapFn = safety.NewMap[string, func(any)]()
// GetValMap can get stored map value with namespace which called BuildSafetyMap, BuildMapFnWithConfirm, BuildMapFn, BuildMapFnWithAnyParams
func GetValMap[K comparable, V any](namespace string) (*safety.Map[K, V], bool) {
m, ok := safetyMaps.Load(namespace)
if !ok {
return nil, false
}
v, ok := m.(*safety.Map[K, V])
return v, ok
}
func DeleteMapVal[T any](namespace string, key ...T) {
fn, ok := deleteMapFn.Load(namespace)
if !ok || len(key) < 1 {
return
}
fn(key)
}
func Reloads(namespaces ...string) {
mut.Lock()
defer mut.Unlock()
hookQueue()
queues := reloadQueues.Load()
for _, name := range namespaces {
i, queue := slice.SearchFirst(queues, func(queue Queue) bool {
return name == queue.Name
})
if i < 0 {
continue
}
queue.Fn()
if queue.Once {
slice.Delete(&queues, i)
reloadQueues.Store(queues)
}
}
}
// BuildMapFnWithConfirm same as BuildMapFn
func BuildMapFnWithConfirm[K comparable, V, A any](namespace string, fn func(A) (V, bool), a ...any) func(key K, args A) V {
m := BuildSafetyMap[K, V, A](namespace, a...)
return func(key K, a A) V {
v, ok := m.Val.Load(key)
if ok {
return v
}
m.Mutex.Lock()
defer m.Mutex.Unlock()
v, ok = m.Val.Load(key)
if ok {
return v
}
v, ok = fn(a)
if ok {
m.Val.Store(key, v)
}
return v
}
}
// BuildMapFn build given fn with a new fn which returned value can be saved and flushed when called Reload or Reloads
// with namespace
//
// if give a float then can be reloaded early or lately, more bigger more earlier
//
// if give a bool false will not flushed when called Reload, then can called GetValMap to flush manually
func BuildMapFn[K comparable, V, A any](namespace string, fn func(A) V, a ...any) func(key K, args A) V {
m := BuildSafetyMap[K, V, A](namespace, a...)
return func(key K, a A) V {
v, ok := m.Val.Load(key)
if ok {
return v
}
m.Mutex.Lock()
defer m.Mutex.Unlock()
v, ok = m.Val.Load(key)
if ok {
return v
}
v = fn(a)
m.Val.Store(key, v)
return v
}
}
// BuildMapFnWithAnyParams same as BuildMapFn use multiple params
func BuildMapFnWithAnyParams[K comparable, V any](namespace string, fn func(...any) V, a ...any) func(key K, a ...any) V {
m := BuildSafetyMap[K, V, any](namespace, a...)
return func(key K, a ...any) V {
v, ok := m.Val.Load(key)
if ok {
return v
}
m.Mutex.Lock()
defer m.Mutex.Unlock()
v, ok = m.Val.Load(key)
if ok {
return v
}
v = fn(a...)
m.Val.Store(key, v)
return v
}
}
func BuildSafetyMap[K comparable, V, A any](namespace string, args ...any) *SafetyMap[K, V, A] {
vv, ok := safetyMaps.Load(namespace)
var m *SafetyMap[K, V, A]
if ok {
m = vv.(*SafetyMap[K, V, A])
return m
}
safetyMapLock.Lock()
defer safetyMapLock.Unlock()
vv, ok = safetyMaps.Load(namespace)
if ok {
m = vv.(*SafetyMap[K, V, A])
return m
}
m = &SafetyMap[K, V, A]{safety.NewMap[K, V](), sync.Mutex{}}
args = append(args, namespace)
deleteMapFn.Store(namespace, func(a any) {
k, ok := a.([]K)
if !ok && len(k) > 0 {
return
}
for _, key := range k {
m.Val.Delete(key)
}
})
Append(m.Val.Flush, args...)
safetyMaps.Store(namespace, m)
return m
}
func GetAnyValMapBy[K comparable, V, A any](namespace string, key K, a A, fn func(A) (V, bool), args ...any) V {
m := BuildSafetyMap[K, V, A](namespace, args...)
v, ok := m.Val.Load(key)
if ok {
return v
}
m.Mutex.Lock()
defer m.Mutex.Unlock()
v, ok = m.Val.Load(key)
if ok {
return v
}
v, ok = fn(a)
if ok {
m.Val.Store(key, v)
}
return v
}
func BuildAnyVal[T, A any](namespace string, args ...any) *SafetyVar[T, A] {
var vv *SafetyVar[T, A]
vvv, ok := safetyMaps.Load(namespace)
if ok {
vv = vvv.(*SafetyVar[T, A])
}
safetyMapLock.Lock()
defer safetyMapLock.Unlock()
vvv, ok = safetyMaps.Load(namespace)
if ok {
vv = vvv.(*SafetyVar[T, A])
return vv
}
v := Val[T]{}
vv = &SafetyVar[T, A]{safety.NewVar(v), sync.Mutex{}}
args = append(args, namespace)
Append(vv.Val.Flush, args...)
safetyMaps.Store(namespace, vv)
return vv
}
func GetAnyValBys[T, A any](namespace string, a A, fn func(A) (T, bool), args ...any) T {
var vv = BuildAnyVal[T, A](namespace, args...)
v := vv.Val.Load()
if v.Ok {
return v.V
}
vv.Mutex.Lock()
defer vv.Mutex.Unlock()
v = vv.Val.Load()
if v.Ok {
return v.V
}
v.V, v.Ok = fn(a)
vv.Val.Store(v)
return v.V
}
// BuildValFnWithConfirm same as BuildValFn
//
// if give a int and value bigger than 1 will be a times which built fn called return false
func BuildValFnWithConfirm[T, A any](namespace string, fn func(A) (T, bool), args ...any) func(A) T {
var vv = BuildAnyVal[T, A](namespace, args...)
tryTimes := helper.ParseArgs(1, args...)
var counter int64
if tryTimes > 1 {
Append(func() {
atomic.StoreInt64(&counter, 0)
}, str.Join("reload-valFn-counter-", namespace))
}
return func(a A) T {
v := vv.Val.Load()
if v.Ok {
return v.V
}
vv.Mutex.Lock()
defer vv.Mutex.Unlock()
v = vv.Val.Load()
if v.Ok {
return v.V
}
v.V, v.Ok = fn(a)
if v.Ok {
vv.Val.Store(v)
return v.V
}
if atomic.LoadInt64(&counter) <= 1 {
return v.V
}
atomic.AddInt64(&counter, 1)
if atomic.LoadInt64(&counter) >= int64(tryTimes) {
v.Ok = true
vv.Val.Store(v)
}
return v.V
}
}
// BuildValFn build given fn a new fn which return value can be saved and flushed when called Reload or Reloads
// with namespace.
//
// note: namespace should be not same as BuildMapFn and related fn, they stored same safety.Map[string,any].
//
// if give a float then can be reloaded early or lately, more bigger more earlier
//
// if give a bool false will not flushed when called Reload, but can call GetValMap or Reloads to flush manually
func BuildValFn[T, A any](namespace string, fn func(A) T, args ...any) func(A) T {
var vv = BuildAnyVal[T, A](namespace, args...)
return func(a A) T {
v := vv.Val.Load()
if v.Ok {
return v.V
}
vv.Mutex.Lock()
defer vv.Mutex.Unlock()
v = vv.Val.Load()
if v.Ok {
return v.V
}
v.V = fn(a)
v.Ok = true
vv.Val.Store(v)
return v.V
}
}
// BuildValFnWithAnyParams same as BuildValFn use multiple params
func BuildValFnWithAnyParams[T any](namespace string, fn func(...any) T, args ...any) func(...any) T {
var vv = BuildAnyVal[T, any](namespace, args...)
return func(a ...any) T {
v := vv.Val.Load()
if v.Ok {
return v.V
}
vv.Mutex.Lock()
defer vv.Mutex.Unlock()
v = vv.Val.Load()
if v.Ok {
return v.V
}
v.V = fn(a...)
v.Ok = true
vv.Val.Store(v)
return v.V
}
}
// Vars get default value and whenever reloaded assign default value
//
// args same as Append
//
// if give a name, then can be flushed by calls Reloads
//
// if give a float then can be reloaded early or lately, more bigger more earlier
//
// if give a bool false will not flushed when called Reload, but can call GetValMap or Reloads to flush manually
//
// if give a int 1 will only execute once when called Reload or Reloads and then delete the flush fn
func Vars[T any](defaults T, args ...any) *safety.Var[T] {
ss := safety.NewVar(defaults)
Append(func() {
ss.Store(defaults)
}, args...)
return ss
}
func parseArgs(a ...any) (ord float64, name string) {
if len(a) > 0 {
for _, arg := range a {
v, ok := arg.(float64)
if ok {
ord = v
}
vv, ok := arg.(string)
if ok {
name = vv
}
}
}
return ord, name
}
// VarsBy
//
// args same as Append
// if give a name, then can be flushed by calls Reloads
func VarsBy[T any](fn func() T, args ...any) *safety.Var[T] {
ss := safety.NewVar(fn())
Append(func() {
ss.Store(fn())
}, args...)
return ss
}
func MapBy[K comparable, T any](fn func(*safety.Map[K, T]), args ...any) *safety.Map[K, T] {
m := safety.NewMap[K, T]()
if fn != nil {
fn(m)
}
Append(func() {
m.Flush()
if fn != nil {
fn(m)
}
}, args...)
return m
}
func SafeMap[K comparable, T any](args ...any) *safety.Map[K, T] {
m := safety.NewMap[K, T]()
Append(m.Flush, args...)
return m
}
// Append the func that will be called whenever Reload called
//
// if give a name, then can be called by called Reloads
//
// if give a float then can be called early or lately when called Reload, more bigger more earlier
//
// if give a bool false will not execute when called Reload, then can called Reloads to execute manually
//
// if give a int 1 will only execute once when called Reload or Reloads and then delete the Queue
func Append(fn func(), a ...any) {
ord, name := parseArgs(a...)
autoExec := helper.ParseArgs(true, a...)
once := helper.ParseArgs(0, a...)
queues := reloadQueues.Load()
queue := Queue{fn, ord, name, autoExec, once == 1}
if name != "" {
i, _ := slice.SearchFirst(queues, func(queue Queue) bool {
return queue.Name == name
})
if i > -1 {
queues[i] = queue
reloadQueues.Store(queues)
return
}
}
reloadQueues.Append(queue)
}
// AppendOnceFn function and args same as Append, but func will execute only once when called Reload or Reloads and then will be deleted. Especially suitable for using to develop plugins, when uninstall plugin can clean or recover some progress's self relative data or behavior which was changed by plugin.
func AppendOnceFn(fn func(), a ...any) {
a = append([]any{1}, a...)
Append(fn, a...)
}
func Reload() {
mut.Lock()
defer mut.Unlock()
deleteMapFn.Flush()
hookQueue()
queues := reloadQueues.Load()
length := len(queues)
slice.SimpleSort(queues, slice.DESC, func(t Queue) float64 {
return t.Order
})
for i, queue := range queues {
if !queue.AutoExec {
continue
}
queue.Fn()
if queue.Once {
slice.Delete(&queues, i)
}
}
if length != len(queues) {
reloadQueues.Store(queues)
}
}
type Any[T any] struct {
fn func() T
v *safety.Var[T]
isManual *safety.Var[bool]
}
// BuildFnVal build a new fn which can be set value by SetFnVal with name or set default value by given fn when called Reload
func BuildFnVal[T any](name string, t T, fn func() T) func() T {
if fn == nil {
fn = func() T {
return t
}
} else {
t = fn()
}
p := safety.NewVar(t)
e := Any[T]{
fn: fn,
v: p,
isManual: safety.NewVar(false),
}
Append(func() {
if !e.isManual.Load() {
e.v.Store(fn())
}
}, str.Join("fnval-", name))
setFnVal.Store(name, e)
return func() T {
return e.v.Load()
}
}
func SetFnVal[T any](name string, val T, onlyManual bool) {
v, ok := setFnVal.Load(name)
if !ok {
return
}
vv, ok := v.(Any[T])
if !ok {
return
}
if onlyManual && !vv.isManual.Load() {
vv.isManual.Store(true)
}
vv.v.Store(val)
}

47
cache/reload/reload_test.go vendored Normal file
View File

@ -0,0 +1,47 @@
package reload
import (
"fmt"
"testing"
)
func TestFlushMapVal(t *testing.T) {
t.Run("t1", func(t *testing.T) {
c := 0
v := GetAnyValMapBy("key", 2, struct{}{}, func(a struct{}) (int, bool) {
c++
return 33, true
})
fmt.Println(v)
DeleteMapVal("key", 2)
v = GetAnyValMapBy("key", 2, struct{}{}, func(a struct{}) (int, bool) {
fmt.Println("xxxxx")
return 33, true
})
fmt.Println(v)
Reloads("key")
v = GetAnyValMapBy[int, int, struct{}]("key", 2, struct{}{}, func(a struct{}) (int, bool) {
fmt.Println("yyyy")
return 33, true
})
fmt.Println(v)
})
}
func TestGetAnyMapFnBys(t *testing.T) {
var i int
t.Run("t1", func(t *testing.T) {
v := BuildMapFnWithConfirm[int]("name", func(a int) (int, bool) {
i++
return a + 1, true
})
vv := v(1, 2)
vvv := v(2, 3)
fmt.Println(vv, vvv)
v(1, 2)
DeleteMapVal("name", 2)
v(2, 3)
fmt.Println(i)
})
}

239
cache/vars.go vendored
View File

@ -2,98 +2,197 @@ package cache
import ( import (
"context" "context"
"errors" "github.com/fthvgb1/wp-go/helper"
"fmt"
"github.com/fthvgb1/wp-go/safety" "github.com/fthvgb1/wp-go/safety"
"sync" "sync"
"time" "time"
) )
type VarCache[T any] struct { type VarCache[T any] struct {
v *safety.Var[vars[T]] AnyCache[T]
setCacheFunc func(context.Context, ...any) (T, error)
mutex sync.Mutex
increaseUpdate *IncreaseUpdateVar[T]
refresh RefreshVar[T]
get func(ctx context.Context) (T, bool)
set func(ctx context.Context, v T)
flush func(ctx context.Context)
getLastSetTime func(ctx context.Context) time.Time
} }
type vars[T any] struct { type IncreaseUpdateVar[T any] struct {
data T CycleTime func() time.Duration
mutex *sync.Mutex Fn IncreaseVarFn[T]
setCacheFunc func(...any) (T, error)
expireTime time.Duration
setTime time.Time
incr int
} }
func (c *VarCache[T]) GetLastSetTime() time.Time { type IncreaseVarFn[T any] func(c context.Context, currentData T, t time.Time, a ...any) (data T, save bool, refresh bool, err error)
return c.v.Load().setTime
func (t *VarCache[T]) Get(ctx context.Context) (T, bool) {
return t.get(ctx)
}
func (t *VarCache[T]) Set(ctx context.Context, v T) {
t.set(ctx, v)
}
func (t *VarCache[T]) Flush(ctx context.Context) {
t.flush(ctx)
}
func (t *VarCache[T]) GetLastSetTime(ctx context.Context) time.Time {
return t.getLastSetTime(ctx)
} }
func NewVarCache[T any](fun func(...any) (T, error), duration time.Duration) *VarCache[T] { func initVarCache[T any](t *VarCache[T], a ...any) {
return &VarCache[T]{ gets := helper.ParseArgs[func(AnyCache[T], context.Context) (T, bool)](nil, a...)
v: safety.NewVar(vars[T]{ if gets == nil {
mutex: &sync.Mutex{}, t.get = t.AnyCache.Get
setCacheFunc: fun, } else {
expireTime: duration, t.get = func(ctx context.Context) (T, bool) {
}), return gets(t.AnyCache, ctx)
}
}
set := helper.ParseArgs[func(AnyCache[T], context.Context, T)](nil, a...)
if set == nil {
t.set = t.AnyCache.Set
} else {
t.set = func(ctx context.Context, v T) {
set(t.AnyCache, ctx, v)
}
}
flush := helper.ParseArgs[func(AnyCache[T], context.Context)](nil, a...)
if flush == nil {
t.flush = t.AnyCache.Flush
} else {
t.flush = func(ctx context.Context) {
flush(t.AnyCache, ctx)
}
}
getLastSetTime := helper.ParseArgs[func(AnyCache[T], context.Context) time.Time](nil, a...)
if getLastSetTime == nil {
t.getLastSetTime = t.AnyCache.GetLastSetTime
} else {
t.getLastSetTime = func(ctx context.Context) time.Time {
return getLastSetTime(t.AnyCache, ctx)
}
} }
} }
func (c *VarCache[T]) IsExpired() bool { func NewVarCache[T any](cache AnyCache[T], fn func(context.Context, ...any) (T, error), inc *IncreaseUpdateVar[T], ref RefreshVar[T], a ...any) *VarCache[T] {
v := c.v.Load() r := &VarCache[T]{
return time.Duration(v.setTime.UnixNano())+v.expireTime < time.Duration(time.Now().UnixNano()) AnyCache: cache, setCacheFunc: fn, mutex: sync.Mutex{},
increaseUpdate: inc,
refresh: ref,
}
initVarCache(r, a...)
return r
} }
func (c *VarCache[T]) Flush() { func (t *VarCache[T]) GetCache(ctx context.Context, timeout time.Duration, params ...any) (T, error) {
v := c.v.Load() data, ok := t.Get(ctx)
mu := v.mutex
mu.Lock()
defer mu.Unlock()
var vv T
v.data = vv
c.v.Store(v)
}
func (c *VarCache[T]) GetCache(ctx context.Context, timeout time.Duration, params ...any) (T, error) {
v := c.v.Load()
data := v.data
var err error var err error
if v.expireTime <= 0 || ((time.Duration(v.setTime.UnixNano()) + v.expireTime) < time.Duration(time.Now().UnixNano())) { if ok {
t := v.incr if t.increaseUpdate != nil && t.refresh != nil {
call := func() { nowTime := time.Now()
v.mutex.Lock() if t.increaseUpdate.CycleTime() > nowTime.Sub(t.GetLastSetTime(ctx)) {
defer v.mutex.Unlock() return data, nil
vv := c.v.Load()
if vv.incr > t {
return
} }
r, er := vv.setCacheFunc(params...) fn := func() {
if err != nil { t.mutex.Lock()
err = er defer t.mutex.Unlock()
return da, save, refresh, er := t.increaseUpdate.Fn(ctx, data, t.GetLastSetTime(ctx), params...)
if er != nil {
err = er
return
}
if save {
t.Set(ctx, da)
}
if refresh {
t.refresh.Refresh(ctx, params...)
}
}
if timeout > 0 {
er := helper.RunFnWithTimeout(ctx, timeout, fn, "increaseUpdate cache fail")
if err == nil && er != nil {
err = er
}
} else {
fn()
} }
vv.setTime = time.Now()
vv.data = r
data = r
vv.incr++
c.v.Store(vv)
} }
if timeout > 0 { return data, nil
ctx, cancel := context.WithTimeout(ctx, timeout) }
defer cancel() call := func() {
done := make(chan struct{}, 1) t.mutex.Lock()
go func() { defer t.mutex.Unlock()
call() dat, ok := t.Get(ctx)
done <- struct{}{} if ok {
close(done) data = dat
}() return
select {
case <-ctx.Done():
err = errors.New(fmt.Sprintf("get cache %s", ctx.Err().Error()))
case <-done:
}
} else {
call()
} }
r, er := t.setCacheFunc(ctx, params...)
if er != nil {
err = er
return
}
t.Set(ctx, r)
data = r
}
if timeout > 0 {
er := helper.RunFnWithTimeout(ctx, timeout, call, "get cache fail")
if err == nil && er != nil {
err = er
}
} else {
call()
} }
return data, err return data, err
} }
type VarMemoryCache[T any] struct {
v *safety.Var[vars[T]]
expireTime func() time.Duration
}
func (c *VarMemoryCache[T]) ClearExpired(ctx context.Context) {
_, ok := c.Get(ctx)
if !ok {
c.Flush(ctx)
}
}
func NewVarMemoryCache[T any](expireTime func() time.Duration) *VarMemoryCache[T] {
return &VarMemoryCache[T]{v: safety.NewVar(vars[T]{}), expireTime: expireTime}
}
func (c *VarMemoryCache[T]) Get(_ context.Context) (T, bool) {
v := c.v.Load()
return v.data, c.expireTime() >= time.Now().Sub(v.setTime)
}
func (c *VarMemoryCache[T]) Set(_ context.Context, v T) {
vv := c.v.Load()
vv.data = v
vv.setTime = time.Now()
vv.incr++
c.v.Store(vv)
}
func (c *VarMemoryCache[T]) SetExpiredTime(f func() time.Duration) {
c.expireTime = f
}
type vars[T any] struct {
data T
setTime time.Time
incr int
}
func (c *VarMemoryCache[T]) GetLastSetTime(_ context.Context) time.Time {
return c.v.Load().setTime
}
func (c *VarMemoryCache[T]) Flush(_ context.Context) {
c.v.Flush()
}

41
cache/vars_test.go vendored
View File

@ -7,9 +7,11 @@ import (
"time" "time"
) )
var cc = *NewVarCache(func(a ...any) (int, error) { var cc = *NewVarCache[int](NewVarMemoryCache[int](func() time.Duration {
return time.Minute
}), func(ctx context.Context, a ...any) (int, error) {
return 1, nil return 1, nil
}, time.Minute) })
func TestVarCache_Flush(t *testing.T) { func TestVarCache_Flush(t *testing.T) {
type testCase[T any] struct { type testCase[T any] struct {
@ -26,41 +28,8 @@ func TestVarCache_Flush(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
fmt.Println(tt.c.GetCache(c, time.Second)) fmt.Println(tt.c.GetCache(c, time.Second))
tt.c.Flush() tt.c.Flush(ctx)
fmt.Println(tt.c.GetCache(c, time.Second)) fmt.Println(tt.c.GetCache(c, time.Second))
}) })
} }
} }
func TestVarCache_IsExpired(t *testing.T) {
type testCase[T any] struct {
name string
c VarCache[T]
want bool
}
tests := []testCase[int]{
{
name: "expired",
c: cc,
want: true,
},
{
name: "not expired",
c: func() VarCache[int] {
v := *NewVarCache(func(a ...any) (int, error) {
return 1, nil
}, time.Minute)
_, _ = v.GetCache(context.Background(), time.Second)
return v
}(),
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.c.IsExpired(); got != tt.want {
t.Errorf("IsExpired() = %v, want %v", got, tt.want)
}
})
}
}

Some files were not shown because too many files have changed in this diff Show More