Compare commits
114 Commits
scriptload
...
master
Author | SHA1 | Date | |
---|---|---|---|
8aeec60181 | |||
58d3e4f645 | |||
e592a753bf | |||
d598fb5ce5 | |||
63f769796f | |||
7cbfdf8601 | |||
9c60d10568 | |||
dcbe760f09 | |||
d220bb2a6c | |||
eb7ccd4f2d | |||
39c9dc1b09 | |||
68beb0fcbb | |||
1f5f7f465d | |||
b7c09cb5a2 | |||
5942f76972 | |||
1f40810b78 | |||
da858d55e0 | |||
166bb08ed0 | |||
5eaf798a6c | |||
e2e6bcc8ce | |||
daa7101493 | |||
6f51f1c295 | |||
a8d1dcdd5b | |||
9af2257650 | |||
63591899bb | |||
ee9ba3fcf0 | |||
a78815f3d3 | |||
6209f2b5e5 | |||
08d3bca39c | |||
9b8e597066 | |||
3215b0c8ea | |||
c7c97c469f | |||
b45ad0e54e | |||
a0c6e48e83 | |||
3c3ed73971 | |||
be58a35245 | |||
67bdf1e070 | |||
981e0ad85b | |||
74159940b5 | |||
206e5902d1 | |||
2d38b36525 | |||
af712f06a5 | |||
73575965d3 | |||
f49ad731e3 | |||
802c7c7dcc | |||
20ea36c727 | |||
fd3199a83c | |||
e25f679ea6 | |||
4842d53316 | |||
65bfccdd48 | |||
c60146d614 | |||
9285698ee4 | |||
5e18c9babd | |||
8bdc53d175 | |||
ad9bdc7574 | |||
0352dd0fd0 | |||
6044b8eaec | |||
8d3197ee98 | |||
8fcf3ecca2 | |||
4d9d011213 | |||
12b75b9d82 | |||
568ab15a34 | |||
c38b62c82a | |||
0774b122ee | |||
f7d2377101 | |||
088fc306de | |||
57d345e445 | |||
f257a966e6 | |||
ceffeccf8d | |||
eed45f51ba | |||
d72bed0c8c | |||
0cdb3ba040 | |||
227de8bdc8 | |||
74304b5b12 | |||
ac29cf2448 | |||
137b2a9738 | |||
9f49a274cd | |||
7c3f8baaa2 | |||
547d8e59e6 | |||
7bbc961f72 | |||
66c02f7fc9 | |||
a5294e2e20 | |||
7978e9ee74 | |||
f6a5cf4255 | |||
928a608878 | |||
42d2795a05 | |||
041d06104b | |||
d1fb560578 | |||
42484e3f96 | |||
64a2c2e33b | |||
86d1616732 | |||
2eb58f732b | |||
936d033547 | |||
cd4787991d | |||
71ddf299e4 | |||
0f467bbc98 | |||
171b3bc59c | |||
f369cf4f22 | |||
ddf6e62e5a | |||
e502f581e1 | |||
acb064b762 | |||
f0c1744998 | |||
876a81c062 | |||
78f5157efe | |||
acbcbf455f | |||
179545f2bb | |||
48a3fe6588 | |||
3fd4f412b1 | |||
5273f4ecf9 | |||
b9c2527f4c | |||
1286338af0 | |||
21a4ff3b3e | |||
2302aa7ff8 | |||
bc71be7238 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -4,3 +4,4 @@
|
||||
err.log
|
||||
/plugins/
|
||||
/config.json
|
||||
go.sum
|
@ -1,8 +1,7 @@
|
||||
FROM golang:latest as gobulidIso
|
||||
FROM golang:1.22.2-alpine as gobulidIso
|
||||
COPY ./ /go/src/wp-go
|
||||
WORKDIR /go/src/wp-go
|
||||
ENV GOPROXY="https://goproxy.cn"
|
||||
RUN go build -ldflags "-w" -tags netgo -o wp-go internal/cmd/main.go
|
||||
RUN go build -ldflags "-w" -tags netgo -o wp-go app/cmd/main.go
|
||||
|
||||
FROM alpine:latest
|
||||
WORKDIR /opt/wp-go
|
||||
|
14
README.md
14
README.md
@ -20,6 +20,11 @@
|
||||
- kill -SIGUSR1 PID 更新配置和清空缓存
|
||||
- kill -SIGUSR2 PID 清空缓存
|
||||
|
||||
#### 运行
|
||||
```
|
||||
go run app/cmd/main.go [-c configpath] [-p port]
|
||||
```
|
||||
|
||||
#### 数据显示支持程度
|
||||
|
||||
| 页表 | 支持程度 |
|
||||
@ -48,7 +53,9 @@
|
||||
- 博客页面至多显示数量
|
||||
- Feed中显示最近数量
|
||||
- 讨论
|
||||
- 其他评论设置-启用评论嵌套,最多嵌套层数
|
||||
- 其他评论设置
|
||||
- `启用|禁止`评论嵌套,最多嵌套层数
|
||||
- 分页显示评论,每页显示评论条数,默认显示`最前/后`页
|
||||
- 在每个页面顶部显示 `新旧`评论
|
||||
|
||||
#### 主题支持程度
|
||||
@ -73,3 +80,8 @@
|
||||
#### 其它
|
||||
|
||||
用的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>
|
||||
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"github.com/fthvgb1/wp-go/app/pkg/logs"
|
||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
||||
"github.com/fthvgb1/wp-go/helper/slice"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"io"
|
||||
@ -77,7 +78,7 @@ func PostComment(c *gin.Context) {
|
||||
}
|
||||
req.Host = home.Host
|
||||
res, err := cli.Do(req)
|
||||
if err != nil && err != http.ErrUseLastResponse {
|
||||
if err != nil && !errors.Is(err, http.ErrUseLastResponse) {
|
||||
logs.Error(err, "请求评论接口错误")
|
||||
return
|
||||
}
|
||||
@ -110,6 +111,9 @@ func PostComment(c *gin.Context) {
|
||||
}
|
||||
cc := c.Copy()
|
||||
go func() {
|
||||
if gin.Mode() != gin.ReleaseMode {
|
||||
return
|
||||
}
|
||||
id := comment.CommentPostId
|
||||
if id <= 0 {
|
||||
logs.Error(errors.New("获取文档id错误"), "", comment.CommentPostId)
|
||||
@ -130,8 +134,15 @@ func PostComment(c *gin.Context) {
|
||||
err = er
|
||||
return
|
||||
}
|
||||
uuu := ""
|
||||
uu, _ := url.Parse(res.Header.Get("Location"))
|
||||
if up.RawQuery != "" {
|
||||
cache.NewCommentCache().Set(c, up.RawQuery, string(s))
|
||||
c.Redirect(http.StatusFound, res.Header.Get("Location"))
|
||||
uuu = str.Join(uu.Path, "?", uu.RawQuery)
|
||||
} else {
|
||||
uuu = str.Join(uu.Path)
|
||||
}
|
||||
c.Redirect(http.StatusFound, uuu)
|
||||
return
|
||||
}
|
||||
var r io.Reader
|
||||
|
@ -10,6 +10,30 @@ import (
|
||||
|
||||
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 {
|
||||
eTag := str.Md5(lastTime.Format(tmp))
|
||||
since := c.Request.Header.Get("If-Modified-Since")
|
||||
@ -24,20 +48,21 @@ func isCacheExpired(c *gin.Context, lastTime time.Time) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func Feed(c *gin.Context) {
|
||||
if !isCacheExpired(c, cache.FeedCache().GetLastSetTime()) {
|
||||
func SiteFeed(c *gin.Context) {
|
||||
feed := cache.FeedCache()
|
||||
if !isCacheExpired(c, feed.GetLastSetTime(c)) {
|
||||
c.Status(http.StatusNotModified)
|
||||
return
|
||||
}
|
||||
|
||||
r, err := cache.FeedCache().GetCache(c, time.Second, c)
|
||||
r, err := feed.GetCache(c, time.Second, c)
|
||||
if err != nil {
|
||||
c.Status(http.StatusInternalServerError)
|
||||
c.Abort()
|
||||
c.Error(err)
|
||||
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) {
|
||||
@ -51,31 +76,33 @@ func setFeed(s string, c *gin.Context, t time.Time) {
|
||||
|
||||
func PostFeed(c *gin.Context) {
|
||||
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)
|
||||
return
|
||||
}
|
||||
s, err := cache.PostFeedCache().GetCache(c, id, time.Second, c, id)
|
||||
s, err := postFeed.GetCache(c, id, time.Second)
|
||||
if err != nil {
|
||||
c.Status(http.StatusInternalServerError)
|
||||
c.Abort()
|
||||
c.Error(err)
|
||||
return
|
||||
}
|
||||
setFeed(s, c, cache.PostFeedCache().GetLastSetTime(c, id))
|
||||
setFeed(s, c, postFeed.GetLastSetTime(c, id))
|
||||
}
|
||||
|
||||
func CommentsFeed(c *gin.Context) {
|
||||
if !isCacheExpired(c, cache.CommentsFeedCache().GetLastSetTime()) {
|
||||
feed := cache.CommentsFeedCache()
|
||||
if !isCacheExpired(c, feed.GetLastSetTime(c)) {
|
||||
c.Status(http.StatusNotModified)
|
||||
return
|
||||
}
|
||||
r, err := cache.CommentsFeedCache().GetCache(c, time.Second, c)
|
||||
r, err := feed.GetCache(c, time.Second, c)
|
||||
if err != nil {
|
||||
c.Status(http.StatusInternalServerError)
|
||||
c.Abort()
|
||||
c.Error(err)
|
||||
return
|
||||
}
|
||||
setFeed(r[0], c, cache.CommentsFeedCache().GetLastSetTime())
|
||||
setFeed(r[0], c, feed.GetLastSetTime(c))
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
package actions
|
||||
|
||||
import (
|
||||
"github.com/fthvgb1/wp-go/app/pkg/constraints"
|
||||
"github.com/fthvgb1/wp-go/app/theme"
|
||||
"github.com/fthvgb1/wp-go/app/theme/wp"
|
||||
"github.com/gin-gonic/gin"
|
||||
@ -9,18 +8,8 @@ import (
|
||||
|
||||
func ThemeHook(scene string) func(*gin.Context) {
|
||||
return func(c *gin.Context) {
|
||||
s := scene
|
||||
if scene == constraints.Home {
|
||||
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)
|
||||
t := theme.GetCurrentTheme()
|
||||
h := wp.NewHandle(c, scene, t)
|
||||
theme.Hook(t, h)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -3,25 +3,21 @@ package main
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/app/cmd/cachemanager"
|
||||
"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/ossigns"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/cache"
|
||||
"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"
|
||||
"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/wpconfig"
|
||||
"github.com/fthvgb1/wp-go/cache/cachemanager"
|
||||
"github.com/fthvgb1/wp-go/model"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"regexp"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -29,7 +25,7 @@ var confPath string
|
||||
var address string
|
||||
var intReg = regexp.MustCompile(`^\d`)
|
||||
|
||||
func init() {
|
||||
func inits() {
|
||||
flag.StringVar(&confPath, "c", "config.yaml", "config file support json,yaml or url")
|
||||
flag.StringVar(&address, "p", "", "listen address and port")
|
||||
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() {
|
||||
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()
|
||||
c := config.GetConfig()
|
||||
if c.Ssl.Key != "" && c.Ssl.Cert != "" {
|
||||
|
@ -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()
|
||||
}
|
@ -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
|
||||
}
|
@ -4,7 +4,7 @@ import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/config"
|
||||
"github.com/soxfmr/gomail"
|
||||
"gopkg.in/gomail.v2"
|
||||
"mime"
|
||||
"path"
|
||||
)
|
||||
|
@ -19,9 +19,9 @@ func FlowLimit(maxRequestSleepNum, maxRequestNum int64, sleepTime []time.Duratio
|
||||
}
|
||||
s := safety.Var[[]time.Duration]{}
|
||||
s.Store(sleepTime)
|
||||
fn := func(msn, mn int64, st []time.Duration) {
|
||||
atomic.StoreInt64(&maxRequestSleepNum, msn)
|
||||
atomic.StoreInt64(&maxRequestNum, mn)
|
||||
fn := func(sleepNum, maxNum int64, st []time.Duration) {
|
||||
atomic.StoreInt64(&maxRequestSleepNum, sleepNum)
|
||||
atomic.StoreInt64(&maxRequestNum, maxNum)
|
||||
s.Store(st)
|
||||
}
|
||||
return func(c *gin.Context) {
|
||||
|
@ -25,14 +25,25 @@ func IpLimit(num int64) (func(ctx *gin.Context), func(int64)) {
|
||||
fn(num)
|
||||
|
||||
return func(c *gin.Context) {
|
||||
if atomic.LoadInt64(m.limitNum) <= 0 {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
ip := c.ClientIP()
|
||||
s := false
|
||||
m.mux.RLock()
|
||||
i, ok := m.m[ip]
|
||||
m.mux.RUnlock()
|
||||
|
||||
if !ok {
|
||||
m.mux.Lock()
|
||||
i = new(int64)
|
||||
m.m[ip] = i
|
||||
m.mux.Unlock()
|
||||
}
|
||||
|
||||
defer func() {
|
||||
ii := atomic.LoadInt64(i)
|
||||
if s && ii > 0 {
|
||||
if ii > 0 {
|
||||
atomic.AddInt64(i, -1)
|
||||
if atomic.LoadInt64(i) == 0 {
|
||||
m.mux.Lock()
|
||||
@ -42,20 +53,12 @@ func IpLimit(num int64) (func(ctx *gin.Context), func(int64)) {
|
||||
}
|
||||
}()
|
||||
|
||||
if !ok {
|
||||
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) {
|
||||
if atomic.LoadInt64(i) >= atomic.LoadInt64(m.limitNum) {
|
||||
c.Status(http.StatusForbidden)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
atomic.AddInt64(i, 1)
|
||||
s = true
|
||||
c.Next()
|
||||
}, fn
|
||||
}
|
||||
|
@ -1,16 +1,16 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/fthvgb1/wp-go/app/cmd/reload"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/config"
|
||||
"github.com/fthvgb1/wp-go/cache/reload"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func SearchLimit(num int64) func(ctx *gin.Context) {
|
||||
fn, reFn := IpLimit(num)
|
||||
reload.Push(func() {
|
||||
reload.Append(func() {
|
||||
reFn(config.GetConfig().SingleIpSearchNum)
|
||||
})
|
||||
}, "search-ip-limit-number")
|
||||
return func(c *gin.Context) {
|
||||
if c.Query("s") != "" {
|
||||
fn(c)
|
||||
|
@ -1,8 +1,8 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/fthvgb1/wp-go/app/cmd/reload"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/config"
|
||||
"github.com/fthvgb1/wp-go/cache/reload"
|
||||
"github.com/fthvgb1/wp-go/helper/maps"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
@ -19,7 +19,7 @@ func ValidateServerNames() func(ctx *gin.Context) {
|
||||
}
|
||||
}
|
||||
return m
|
||||
})
|
||||
}, "site-names")
|
||||
|
||||
return func(c *gin.Context) {
|
||||
m := sites.Load()
|
||||
|
69
app/ossigns/signs.go
Normal file
69
app/ossigns/signs.go
Normal 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
148
app/pkg/cache/cache.go
vendored
@ -2,85 +2,96 @@ package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/fthvgb1/wp-go/app/cmd/cachemanager"
|
||||
"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/pkg/models"
|
||||
"github.com/fthvgb1/wp-go/cache"
|
||||
"github.com/fthvgb1/wp-go/helper/slice"
|
||||
"github.com/fthvgb1/wp-go/safety"
|
||||
"github.com/fthvgb1/wp-go/cache/cachemanager"
|
||||
"github.com/fthvgb1/wp-go/cache/reload"
|
||||
"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() {
|
||||
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()
|
||||
}
|
||||
@ -91,19 +102,19 @@ type Arch struct {
|
||||
month time.Month
|
||||
}
|
||||
|
||||
var arch = safety.NewVar(Arch{
|
||||
var arch = reload.Vars(Arch{
|
||||
fn: dao.Archives,
|
||||
})
|
||||
}, "archives-year-month-data")
|
||||
|
||||
func Archives(ctx context.Context) []models.PostArchive {
|
||||
a := arch.Load()
|
||||
data := a.data
|
||||
l := len(data)
|
||||
m := time.Now().Month()
|
||||
if l > 0 && a.month != m || l < 1 {
|
||||
if l < 1 || a.month != m {
|
||||
r, err := a.fn(ctx)
|
||||
if err != nil {
|
||||
logs.Error(err, "set cache fail")
|
||||
logs.Error(err, "set cache Archives fail")
|
||||
return nil
|
||||
}
|
||||
a.month = m
|
||||
@ -113,30 +124,3 @@ func Archives(ctx context.Context) []models.PostArchive {
|
||||
}
|
||||
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
39
app/pkg/cache/categoryandtag.go
vendored
Normal 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)
|
||||
}
|
104
app/pkg/cache/comments.go
vendored
104
app/pkg/cache/comments.go
vendored
@ -2,16 +2,23 @@ package cache
|
||||
|
||||
import (
|
||||
"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/models"
|
||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
||||
"github.com/fthvgb1/wp-go/cache"
|
||||
"github.com/fthvgb1/wp-go/cache/cachemanager"
|
||||
"github.com/fthvgb1/wp-go/helper"
|
||||
"github.com/fthvgb1/wp-go/helper/number"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// RecentComments query func see RecentComment
|
||||
func RecentComments(ctx context.Context, n int) (r []models.Comments) {
|
||||
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 {
|
||||
r = r[0:n]
|
||||
}
|
||||
@ -19,22 +26,99 @@ func RecentComments(ctx context.Context, n int) (r []models.Comments) {
|
||||
return
|
||||
}
|
||||
|
||||
func PostComments(ctx context.Context, Id uint64) ([]models.Comments, error) {
|
||||
ids, err := postCommentCaches.GetCache(ctx, Id, time.Second, ctx, Id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// PostTopLevelCommentIds query func see PostTopComments
|
||||
func PostTopLevelCommentIds(ctx context.Context, postId uint64, page, limit, total int, order string, a ...any) ([]uint64, error) {
|
||||
var key string
|
||||
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) {
|
||||
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) {
|
||||
return commentsCache.GetCacheBatch(ctx, ids, time.Second, ctx, ids)
|
||||
// GetCommentDataByIds query func see dao.GetCommentByIds
|
||||
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] {
|
||||
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
55
app/pkg/cache/feed.go
vendored
@ -1,6 +1,8 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/logs"
|
||||
"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/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"
|
||||
"github.com/fthvgb1/wp-go/plugin/digest"
|
||||
"github.com/fthvgb1/wp-go/rss2"
|
||||
"github.com/gin-gonic/gin"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@ -33,20 +35,25 @@ func InitFeed() {
|
||||
}
|
||||
}
|
||||
|
||||
// CommentsFeedCache query func see CommentsFeed
|
||||
func CommentsFeedCache() *cache.VarCache[[]string] {
|
||||
return commentsFeedCache
|
||||
r, _ := cachemanager.GetVarCache[[]string]("commentsFeed")
|
||||
return r
|
||||
}
|
||||
|
||||
// FeedCache query func see SiteFeed
|
||||
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] {
|
||||
return postFeedCache
|
||||
r, _ := cachemanager.GetMapCache[string, string]("postFeed")
|
||||
return r
|
||||
}
|
||||
|
||||
func feed(arg ...any) (xml []string, err error) {
|
||||
c := arg[0].(*gin.Context)
|
||||
func SiteFeed(c context.Context, _ ...any) (xml []string, err error) {
|
||||
r := RecentPosts(c, 10)
|
||||
ids := slice.Map(r, func(t models.Posts) uint64 {
|
||||
return t.Id
|
||||
@ -92,9 +99,7 @@ func feed(arg ...any) (xml []string, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func postFeed(arg ...any) (x string, err error) {
|
||||
c := arg[0].(*gin.Context)
|
||||
id := arg[1].(string)
|
||||
func PostFeed(c context.Context, id string, _ ...any) (x string, err error) {
|
||||
ID := str.ToInteger[uint64](id, 0)
|
||||
maxId, err := GetMaxPostId(c)
|
||||
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 {
|
||||
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 {
|
||||
return
|
||||
}
|
||||
@ -121,10 +131,14 @@ func postFeed(arg ...any) (x string, err error) {
|
||||
wpposts.PasswdProjectContent(&post)
|
||||
if len(comments) > 0 {
|
||||
t := comments[len(comments)-1]
|
||||
u, err := GetCommentUrl(c, t.CommentId, t.CommentPostId)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
rs.Items = []rss2.Item{
|
||||
{
|
||||
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,
|
||||
PubDate: t.CommentDateGmt.Format(timeFormat),
|
||||
Guid: fmt.Sprintf("%s#comment-%d", post.Guid, t.CommentId),
|
||||
@ -135,9 +149,14 @@ func postFeed(arg ...any) (x string, err error) {
|
||||
}
|
||||
} else {
|
||||
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{
|
||||
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,
|
||||
PubDate: t.CommentDateGmt.Format(timeFormat),
|
||||
Guid: fmt.Sprintf("%s#comment-%d", post.Guid, t.CommentId),
|
||||
@ -150,15 +169,14 @@ func postFeed(arg ...any) (x string, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func commentsFeed(args ...any) (r []string, err error) {
|
||||
c := args[0].(*gin.Context)
|
||||
func CommentsFeed(c context.Context, _ ...any) (r []string, err error) {
|
||||
commens := RecentComments(c, 10)
|
||||
rs := templateRss
|
||||
rs.Title = fmt.Sprintf("\"%s\"的评论", wpconfig.GetOption("blogname"))
|
||||
rs.LastBuildDate = time.Now().Format(timeFormat)
|
||||
site := wpconfig.GetOption("siteurl")
|
||||
rs.AtomLink = fmt.Sprintf("%s/comments/feed", site)
|
||||
com, err := GetCommentByIds(c, slice.Map(commens, func(t models.Comments) uint64 {
|
||||
com, err := GetCommentDataByIds(c, slice.Map(commens, func(t models.Comments) uint64 {
|
||||
return t.CommentId
|
||||
}))
|
||||
if nil != err {
|
||||
@ -175,9 +193,14 @@ func commentsFeed(args ...any) (r []string, err error) {
|
||||
} else {
|
||||
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{
|
||||
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,
|
||||
Description: desc,
|
||||
PubDate: t.CommentDateGmt.Format(timeFormat),
|
||||
|
14
app/pkg/cache/postmeta.go
vendored
14
app/pkg/cache/postmeta.go
vendored
@ -2,14 +2,16 @@ package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/fthvgb1/wp-go/cache/cachemanager"
|
||||
"time"
|
||||
)
|
||||
|
||||
func GetPostMetaByPostIds(ctx context.Context, ids []uint64) (r []map[string]any, err error) {
|
||||
r, err = postMetaCache.GetCacheBatch(ctx, ids, time.Second, ctx, ids)
|
||||
return
|
||||
// GetPostMetaByPostIds query func see dao.GetPostMetaByPostIds
|
||||
func GetPostMetaByPostIds(ctx context.Context, ids []uint64) ([]map[string]any, error) {
|
||||
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)
|
||||
return
|
||||
|
||||
// GetPostMetaByPostId query func see dao.GetPostMetaByPostIds
|
||||
func GetPostMetaByPostId(ctx context.Context, id uint64) (map[string]any, error) {
|
||||
return cachemanager.GetBy[map[string]any]("postMetaData", ctx, id, time.Second)
|
||||
}
|
||||
|
29
app/pkg/cache/posts.go
vendored
29
app/pkg/cache/posts.go
vendored
@ -3,26 +3,30 @@ package cache
|
||||
import (
|
||||
"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/models"
|
||||
"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/slice"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"github.com/gin-gonic/gin"
|
||||
"time"
|
||||
)
|
||||
|
||||
// GetPostById query func see dao.GetPostsByIds
|
||||
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) {
|
||||
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) {
|
||||
ids, err := searchPostIdsCache.GetCache(ctx, key, time.Second, args...)
|
||||
ids, err := cachemanager.GetBy[dao.PostIds]("searchPostIds", ctx, key, time.Second, args...)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -31,8 +35,9 @@ func SearchPost(ctx context.Context, key string, args ...any) (r []models.Posts,
|
||||
return
|
||||
}
|
||||
|
||||
// PostLists query func see dao.SearchPostIds
|
||||
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 {
|
||||
return
|
||||
}
|
||||
@ -41,15 +46,17 @@ func PostLists(ctx context.Context, key string, args ...any) (r []models.Posts,
|
||||
return
|
||||
}
|
||||
|
||||
func GetMaxPostId(ctx *gin.Context) (uint64, error) {
|
||||
return maxPostIdCache.GetCache(ctx, time.Second, ctx)
|
||||
// GetMaxPostId query func see dao.GetMaxPostId
|
||||
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) {
|
||||
nn := n
|
||||
feedNum := str.ToInteger(wpconfig.GetOption("posts_per_rss"), 10)
|
||||
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) {
|
||||
r = r[:n]
|
||||
}
|
||||
@ -57,8 +64,9 @@ func RecentPosts(ctx context.Context, n int) (r []models.Posts) {
|
||||
return
|
||||
}
|
||||
|
||||
// GetContextPost query func see dao.GetPostContext
|
||||
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 {
|
||||
return models.Posts{}, models.Posts{}, err
|
||||
}
|
||||
@ -67,8 +75,9 @@ func GetContextPost(ctx context.Context, id uint64, date time.Time) (prev, next
|
||||
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) {
|
||||
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 {
|
||||
return
|
||||
}
|
||||
|
20
app/pkg/cache/users.go
vendored
20
app/pkg/cache/users.go
vendored
@ -4,27 +4,23 @@ import (
|
||||
"context"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/logs"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
||||
"github.com/fthvgb1/wp-go/model"
|
||||
"github.com/fthvgb1/wp-go/cache/cachemanager"
|
||||
"time"
|
||||
)
|
||||
|
||||
func getUserById(a ...any) (r models.Users, err error) {
|
||||
ctx := a[0].(context.Context)
|
||||
uid := a[1].(uint64)
|
||||
r, err = model.FindOneById[models.Users](ctx, uid)
|
||||
return
|
||||
}
|
||||
|
||||
// GetUserByName query func see dao.GetUserByName
|
||||
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) {
|
||||
return allUsernameCache.GetCache(ctx, time.Second, ctx)
|
||||
// GetAllUsername query func see dao.AllUsername
|
||||
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 {
|
||||
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)
|
||||
return r
|
||||
}
|
||||
|
@ -63,6 +63,7 @@ type CacheTime struct {
|
||||
UserInfoCacheTime time.Duration `yaml:"userInfoCacheTime" json:"userInfoCacheTime,omitempty"`
|
||||
CommentsCacheTime time.Duration `yaml:"commentsCacheTime" json:"commentsCacheTime,omitempty"`
|
||||
SleepTime []time.Duration `yaml:"sleepTime" json:"sleepTime,omitempty"`
|
||||
CommentsIncreaseUpdateTime time.Duration `yaml:"commentsIncreaseUpdateTime" json:"commentsIncreaseUpdateTime"`
|
||||
}
|
||||
|
||||
type Ssl struct {
|
||||
@ -84,6 +85,14 @@ type Mysql struct {
|
||||
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 {
|
||||
if conf == "" {
|
||||
conf = "config.yaml"
|
||||
@ -95,6 +104,7 @@ func InitConfig(conf string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer get.Body.Close()
|
||||
file, err = io.ReadAll(get.Body)
|
||||
} else {
|
||||
file, err = os.ReadFile(conf)
|
||||
@ -102,6 +112,7 @@ func InitConfig(conf string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fileData.Store(file)
|
||||
var c Config
|
||||
err = yaml.Unmarshal(file, &c)
|
||||
if err != nil {
|
||||
|
@ -2,17 +2,21 @@ package dao
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
||||
"github.com/fthvgb1/wp-go/helper"
|
||||
"github.com/fthvgb1/wp-go/helper/number"
|
||||
"github.com/fthvgb1/wp-go/helper/slice"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"github.com/fthvgb1/wp-go/model"
|
||||
)
|
||||
|
||||
// RecentComments
|
||||
// param context.Context
|
||||
func RecentComments(a ...any) (r []models.Comments, err error) {
|
||||
ctx := a[0].(context.Context)
|
||||
n := a[1].(int)
|
||||
func RecentComments(ctx context.Context, a ...any) (r []models.Comments, err error) {
|
||||
n := helper.ParseArgs(10, a...)
|
||||
return model.Finds[models.Comments](ctx, model.Conditions(
|
||||
model.Where(model.SqlBuilder{
|
||||
{"comment_approved", "1"},
|
||||
@ -28,10 +32,8 @@ func RecentComments(a ...any) (r []models.Comments, err error) {
|
||||
// PostComments
|
||||
// param1 context.Context
|
||||
// param2 postId
|
||||
func PostComments(args ...any) ([]uint64, error) {
|
||||
ctx := args[0].(context.Context)
|
||||
postId := args[1].(uint64)
|
||||
r, err := model.Finds[models.Comments](ctx, model.Conditions(
|
||||
func PostComments(ctx context.Context, postId uint64, _ ...any) ([]uint64, error) {
|
||||
r, err := model.ChunkFind[models.Comments](ctx, 300, model.Conditions(
|
||||
model.Where(model.SqlBuilder{
|
||||
{"comment_approved", "1"},
|
||||
{"comment_post_ID", "=", number.IntToString(postId), "int"},
|
||||
@ -50,17 +52,132 @@ func PostComments(args ...any) ([]uint64, error) {
|
||||
}), err
|
||||
}
|
||||
|
||||
func GetCommentByIds(args ...any) (map[uint64]models.Comments, error) {
|
||||
ctx := args[0].(context.Context)
|
||||
ids := args[1].([]uint64)
|
||||
func GetCommentByIds(ctx context.Context, ids []uint64, _ ...any) (map[uint64]models.Comments, error) {
|
||||
if len(ids) < 1 {
|
||||
return nil, nil
|
||||
}
|
||||
m := make(map[uint64]models.Comments)
|
||||
r, err := model.SimpleFind[models.Comments](ctx, model.SqlBuilder{
|
||||
off := 0
|
||||
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"},
|
||||
}, "*", slice.ToAnySlice(ids))
|
||||
}),
|
||||
model.Fields("*"),
|
||||
model.In(slice.ToAnySlice(id)),
|
||||
))
|
||||
if err != nil {
|
||||
return m, err
|
||||
}
|
||||
return slice.SimpleToMap(r, func(t models.Comments) uint64 {
|
||||
return t.CommentId
|
||||
}), 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
|
||||
}
|
||||
|
@ -21,18 +21,14 @@ type PostContext struct {
|
||||
Next models.Posts
|
||||
}
|
||||
|
||||
func CategoriesAndTags(a ...any) (terms []models.TermsMy, err error) {
|
||||
ctx := a[0].(context.Context)
|
||||
t, ok := a[1].(string)
|
||||
func CategoriesAndTags(ctx context.Context, t string, _ ...any) (terms []models.TermsMy, err error) {
|
||||
var in = []any{"category", "post_tag"}
|
||||
if ok {
|
||||
switch t {
|
||||
case constraints.Category:
|
||||
in = []any{"category"}
|
||||
case constraints.Tag:
|
||||
in = []any{"post_tag"}
|
||||
}
|
||||
}
|
||||
w := model.SqlBuilder{
|
||||
{"tt.taxonomy", "in", ""},
|
||||
}
|
||||
|
@ -11,10 +11,8 @@ import (
|
||||
"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)
|
||||
ctx := args[0].(context.Context)
|
||||
ids := args[1].([]uint64)
|
||||
rr, err := model.Finds[models.PostMeta](ctx, model.Conditions(
|
||||
model.Where(model.SqlBuilder{{"post_id", "in", ""}}),
|
||||
model.In(slice.ToAnySlice(ids)),
|
||||
|
@ -3,6 +3,7 @@ package dao
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/models/relation"
|
||||
@ -15,10 +16,8 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func GetPostsByIds(a ...any) (m map[uint64]models.Posts, err error) {
|
||||
ctx := a[0].(context.Context)
|
||||
func GetPostsByIds(ctx context.Context, ids []uint64, _ ...any) (m map[uint64]models.Posts, err error) {
|
||||
m = make(map[uint64]models.Posts)
|
||||
ids := a[1].([]uint64)
|
||||
q := model.Conditions(
|
||||
model.Where(model.SqlBuilder{{"Id", "in", ""}}),
|
||||
model.Join(model.SqlBuilder{
|
||||
@ -99,11 +98,10 @@ func GetPostsByIds(a ...any) (m map[uint64]models.Posts, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func SearchPostIds(args ...any) (ids PostIds, err error) {
|
||||
ctx := args[0].(context.Context)
|
||||
q := args[1].(*model.QueryCondition)
|
||||
page := args[2].(int)
|
||||
pageSize := args[3].(int)
|
||||
func SearchPostIds(ctx context.Context, _ string, args ...any) (ids PostIds, err error) {
|
||||
q := args[0].(*model.QueryCondition)
|
||||
page := args[1].(int)
|
||||
pageSize := args[2].(int)
|
||||
q.Fields = "ID"
|
||||
res, total, err := model.Pagination[models.Posts](ctx, q, page, pageSize)
|
||||
for _, posts := range res {
|
||||
@ -118,11 +116,19 @@ func SearchPostIds(args ...any) (ids PostIds, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func GetMaxPostId(a ...any) (uint64, error) {
|
||||
ctx := a[0].(context.Context)
|
||||
r, err := model.SimpleFind[models.Posts](ctx,
|
||||
model.SqlBuilder{{"post_type", "post"}, {"post_status", "publish"}},
|
||||
"max(ID) ID",
|
||||
func GetMaxPostId(ctx context.Context, _ ...any) (uint64, error) {
|
||||
r, err := model.Finds[models.Posts](ctx,
|
||||
model.Conditions(
|
||||
model.Where(model.SqlBuilder{
|
||||
{"post_type", "post"},
|
||||
{"post_status", "publish"}},
|
||||
),
|
||||
model.Fields("ID"),
|
||||
model.Order(model.SqlBuilder{
|
||||
{"ID", "desc"},
|
||||
}),
|
||||
model.Limit(1),
|
||||
),
|
||||
)
|
||||
var id uint64
|
||||
if len(r) > 0 {
|
||||
@ -131,9 +137,8 @@ func GetMaxPostId(a ...any) (uint64, error) {
|
||||
return id, err
|
||||
}
|
||||
|
||||
func RecentPosts(a ...any) (r []models.Posts, err error) {
|
||||
ctx := a[0].(context.Context)
|
||||
num := a[1].(int)
|
||||
func RecentPosts(ctx context.Context, a ...any) (r []models.Posts, err error) {
|
||||
num := helper.ParseArgs(10, a...)
|
||||
r, err = model.Finds[models.Posts](ctx, model.Conditions(
|
||||
model.Where(model.SqlBuilder{
|
||||
{"post_type", "post"},
|
||||
@ -146,15 +151,14 @@ func RecentPosts(a ...any) (r []models.Posts, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func GetPostContext(arg ...any) (r PostContext, err error) {
|
||||
ctx := arg[0].(context.Context)
|
||||
t := arg[1].(time.Time)
|
||||
func GetPostContext(ctx context.Context, _ uint64, arg ...any) (r PostContext, err error) {
|
||||
t := arg[0].(time.Time)
|
||||
next, err := model.FirstOne[models.Posts](ctx, model.SqlBuilder{
|
||||
{"post_date", ">", t.Format("2006-01-02 15:04:05")},
|
||||
{"post_status", "in", ""},
|
||||
{"post_type", "post"},
|
||||
}, "ID,post_title,post_password", nil, []any{"publish"})
|
||||
if err == sql.ErrNoRows {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
@ -165,7 +169,7 @@ func GetPostContext(arg ...any) (r PostContext, err error) {
|
||||
{"post_status", "in", ""},
|
||||
{"post_type", "post"},
|
||||
}, "ID,post_title", model.SqlBuilder{{"post_date", "desc"}}, []any{"publish"})
|
||||
if err == sql.ErrNoRows {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
@ -178,9 +182,8 @@ func GetPostContext(arg ...any) (r PostContext, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func MonthPost(args ...any) (r []uint64, err error) {
|
||||
ctx := args[0].(context.Context)
|
||||
year, month := args[1].(string), args[2].(string)
|
||||
func MonthPost(ctx context.Context, _ string, args ...any) (r []uint64, err error) {
|
||||
year, month := args[0].(string), args[1].(string)
|
||||
where := model.SqlBuilder{
|
||||
{"post_type", "post"},
|
||||
{"post_status", "publish"},
|
||||
|
@ -7,31 +7,26 @@ import (
|
||||
"github.com/fthvgb1/wp-go/model"
|
||||
)
|
||||
|
||||
func GetUserById(a ...any) (r models.Users, err error) {
|
||||
ctx := a[0].(context.Context)
|
||||
uid := a[1].(uint64)
|
||||
func GetUserById(ctx context.Context, uid uint64, _ ...any) (r models.Users, err error) {
|
||||
r, err = model.FindOneById[models.Users](ctx, uid)
|
||||
return
|
||||
}
|
||||
|
||||
func AllUsername(a ...any) (map[string]struct{}, error) {
|
||||
ctx := a[0].(context.Context)
|
||||
func AllUsername(ctx context.Context, _ ...any) (map[string]uint64, error) {
|
||||
r, err := model.SimpleFind[models.Users](ctx, model.SqlBuilder{
|
||||
{"user_status", "=", "0", "int"},
|
||||
}, "user_login")
|
||||
}, "display_name,ID")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return slice.ToMap(r, func(t models.Users) (string, struct{}) {
|
||||
return t.UserLogin, struct{}{}
|
||||
return slice.ToMap(r, func(t models.Users) (string, uint64) {
|
||||
return t.DisplayName, t.Id
|
||||
}, true), nil
|
||||
}
|
||||
|
||||
func GetUserByName(a ...any) (r models.Users, err error) {
|
||||
u := a[1].(string)
|
||||
ctx := a[0].(context.Context)
|
||||
func GetUserByName(ctx context.Context, u string, _ ...any) (r models.Users, err error) {
|
||||
r, err = model.FirstOne[models.Users](ctx, model.SqlBuilder{{
|
||||
"user_login", u,
|
||||
"display_name", u,
|
||||
}}, "*", nil)
|
||||
return
|
||||
}
|
||||
|
@ -3,14 +3,21 @@ package db
|
||||
import (
|
||||
"context"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/config"
|
||||
"github.com/fthvgb1/wp-go/cache/reload"
|
||||
"github.com/fthvgb1/wp-go/model"
|
||||
"github.com/fthvgb1/wp-go/safety"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"log"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
var safeDb = safety.NewVar[*sqlx.DB](nil)
|
||||
var showQuerySql func() bool
|
||||
|
||||
func GetSqlxDB() *sqlx.DB {
|
||||
return safeDb.Load()
|
||||
}
|
||||
|
||||
func InitDb() (*safety.Var[*sqlx.DB], error) {
|
||||
c := config.GetConfig()
|
||||
@ -36,6 +43,11 @@ func InitDb() (*safety.Var[*sqlx.DB], error) {
|
||||
if preDb != nil {
|
||||
_ = preDb.Close()
|
||||
}
|
||||
if showQuerySql == nil {
|
||||
showQuerySql = reload.BuildFnVal("showQuerySql", false, func() bool {
|
||||
return config.GetConfig().ShowQuerySql
|
||||
})
|
||||
}
|
||||
return safeDb, err
|
||||
}
|
||||
|
||||
@ -44,14 +56,20 @@ func QueryDb(db *safety.Var[*sqlx.DB]) *model.SqlxQuery {
|
||||
nil,
|
||||
nil))
|
||||
model.SetSelect(query, func(ctx context.Context, a any, s string, args ...any) error {
|
||||
if config.GetConfig().ShowQuerySql {
|
||||
go log.Println(model.FormatSql(s, args...))
|
||||
if showQuerySql() {
|
||||
_, f, l, _ := runtime.Caller(5)
|
||||
go func() {
|
||||
log.Printf("%v:%v %v\n", f, l, model.FormatSql(s, args...))
|
||||
}()
|
||||
}
|
||||
return query.Selects(ctx, a, s, args...)
|
||||
})
|
||||
model.SetGet(query, func(ctx context.Context, a any, s string, args ...any) error {
|
||||
if config.GetConfig().ShowQuerySql {
|
||||
go log.Println(model.FormatSql(s, args...))
|
||||
if showQuerySql() {
|
||||
_, f, l, _ := runtime.Caller(5)
|
||||
go func() {
|
||||
log.Printf("%v:%v %v\n", f, l, model.FormatSql(s, args...))
|
||||
}()
|
||||
}
|
||||
return query.Gets(ctx, a, s, args...)
|
||||
})
|
||||
|
@ -15,20 +15,24 @@ var logs = safety.NewVar[*log.Logger](nil)
|
||||
var logFile = safety.NewVar[*os.File](nil)
|
||||
|
||||
func InitLogger() error {
|
||||
c := config.GetConfig()
|
||||
return SetLogger(c.LogOutput)
|
||||
}
|
||||
|
||||
func SetLogger(loggerFile string) error {
|
||||
if loggerFile == "" {
|
||||
loggerFile = "stderr"
|
||||
}
|
||||
preFD := logFile.Load()
|
||||
l := &log.Logger{}
|
||||
c := config.GetConfig()
|
||||
if c.LogOutput == "" {
|
||||
c.LogOutput = "stderr"
|
||||
}
|
||||
var out io.Writer
|
||||
switch c.LogOutput {
|
||||
switch loggerFile {
|
||||
case "stdout":
|
||||
out = os.Stdout
|
||||
case "stderr":
|
||||
out = os.Stderr
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ type Comments struct {
|
||||
UserId uint64 `gorm:"column:user_id" db:"user_id" json:"user_id" form:"user_id"`
|
||||
//扩展字段
|
||||
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 {
|
||||
@ -29,3 +30,8 @@ func (w Comments) PrimaryKey() string {
|
||||
func (w Comments) Table() string {
|
||||
return "wp_comments"
|
||||
}
|
||||
|
||||
type PostComments struct {
|
||||
Comments
|
||||
Children []uint64
|
||||
}
|
||||
|
@ -1,12 +1,13 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
||||
"github.com/fthvgb1/wp-go/helper/number"
|
||||
"github.com/fthvgb1/wp-go/helper/slice"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
@ -18,6 +19,7 @@ type CommentHandler struct {
|
||||
depth int
|
||||
isTls bool
|
||||
i CommentHtml
|
||||
isThreadComments bool
|
||||
}
|
||||
|
||||
type Comments struct {
|
||||
@ -26,19 +28,18 @@ type Comments struct {
|
||||
}
|
||||
|
||||
type CommentHtml interface {
|
||||
Sort(i, j *Comments) bool
|
||||
FormatLi(c *gin.Context, m models.Comments, depth int, isTls bool, eo, parent string) string
|
||||
FormatLi(c context.Context, m models.Comments, depth, maxDepth, page int, isTls, isThreadComments bool, eo, parent string) string
|
||||
FloorOrder(i, j models.Comments) bool
|
||||
}
|
||||
|
||||
func FormatComments(c *gin.Context, i CommentHtml, comments []models.Comments, maxDepth int) string {
|
||||
tree := treeComments(comments)
|
||||
u := c.Request.Header.Get("Referer")
|
||||
|
||||
var isTls bool
|
||||
if u != "" {
|
||||
uu, _ := url.Parse(u)
|
||||
if uu.Scheme == "https" {
|
||||
if c.Request.TLS != nil {
|
||||
isTls = true
|
||||
}
|
||||
} else {
|
||||
isTls = "https" == strings.ToLower(c.Request.Header.Get("X-Forwarded-Proto"))
|
||||
}
|
||||
h := CommentHandler{
|
||||
Context: c,
|
||||
@ -48,15 +49,21 @@ func FormatComments(c *gin.Context, i CommentHtml, comments []models.Comments, m
|
||||
isTls: isTls,
|
||||
i: i,
|
||||
}
|
||||
return h.formatComment(h.comments, true)
|
||||
return h.formatComment(h.comments)
|
||||
}
|
||||
|
||||
func (d CommentHandler) formatComment(comments []*Comments, isTop bool) (html string) {
|
||||
func (d CommentHandler) formatComment(comments []*Comments) (html string) {
|
||||
s := str.NewBuilder()
|
||||
if d.depth > d.maxDepth {
|
||||
if d.depth >= d.maxDepth {
|
||||
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 {
|
||||
eo := "even"
|
||||
if (i+1)%2 == 0 {
|
||||
@ -68,13 +75,11 @@ func (d CommentHandler) formatComment(comments []*Comments, isTop bool) (html st
|
||||
parent = "parent"
|
||||
fl = true
|
||||
}
|
||||
s.WriteString(d.i.FormatLi(d.Context, comment.Comments, d.depth, d.isTls, eo, parent))
|
||||
s.WriteString(d.i.FormatLi(d.Context, comment.Comments, d.depth, d.maxDepth, 1, d.isTls, d.isThreadComments, eo, parent))
|
||||
if fl {
|
||||
d.depth++
|
||||
s.WriteString(`<ol class="children">`, d.formatComment(comment.Children, false), `</ol>`)
|
||||
if isTop {
|
||||
d.depth = 1
|
||||
}
|
||||
s.WriteString(`<ol class="children">`, d.formatComment(comment.Children), `</ol>`)
|
||||
d.depth--
|
||||
}
|
||||
s.WriteString("</li><!-- #comment-## -->")
|
||||
}
|
||||
@ -87,7 +92,7 @@ func (d CommentHandler) findComments(comments []*Comments) []*Comments {
|
||||
var r []*Comments
|
||||
for _, comment := range comments {
|
||||
tmp := *comment
|
||||
comment.Children = nil
|
||||
tmp.Children = nil
|
||||
r = append(r, &tmp)
|
||||
if len(comment.Children) > 0 {
|
||||
t := d.findComments(comment.Children)
|
||||
@ -137,16 +142,35 @@ func CommentRender() CommonCommentFormat {
|
||||
type CommonCommentFormat struct {
|
||||
}
|
||||
|
||||
func (c CommonCommentFormat) Sort(i, j *Comments) bool {
|
||||
order := wpconfig.GetOption("comment_order")
|
||||
if order == "asc" {
|
||||
return i.CommentDate.UnixNano() < j.CommentDate.UnixNano()
|
||||
}
|
||||
return i.CommentDate.UnixNano() > j.CommentDate.UnixNano()
|
||||
func (c CommonCommentFormat) FormatLi(_ context.Context, m models.Comments, currentDepth, maxDepth, page int, isTls, isThreadComments bool, eo, parent string) string {
|
||||
return FormatLi(li, m, respondsFn, currentDepth, maxDepth, page, isTls, isThreadComments, eo, parent)
|
||||
}
|
||||
|
||||
func (c CommonCommentFormat) FormatLi(ctx *gin.Context, m models.Comments, depth int, isTls bool, eo, parent string) string {
|
||||
return FormatLi(CommonLi(), ctx, m, depth, isTls, eo, parent)
|
||||
func (c CommonCommentFormat) FloorOrder(i, j models.Comments) bool {
|
||||
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 = `
|
||||
@ -158,14 +182,11 @@ var li = `
|
||||
src="{{Gravatar}}"
|
||||
srcset="{{Gravatar}} 2x"
|
||||
class="avatar avatar-56 photo" height="56" width="56" loading="lazy">
|
||||
<b class="fn">
|
||||
<a href="{{CommentAuthorUrl}}" rel="external nofollow ugc"
|
||||
class="url">{{CommentAuthor}}</a>
|
||||
</b>
|
||||
<b class="fn">{{CommentAuthor}}</b>
|
||||
<span class="says">说道:</span></div><!-- .comment-author -->
|
||||
|
||||
<div class="comment-metadata">
|
||||
<a href="/p/{{PostId}}#comment-{{CommentId}}">
|
||||
<a href="/p/{{PostId}}/comment-page-{{page}}#comment-{{CommentId}}">
|
||||
<time datetime="{{CommentDateGmt}}">{{CommentDate}}</time>
|
||||
</a></div><!-- .comment-metadata -->
|
||||
|
||||
@ -175,30 +196,34 @@ var li = `
|
||||
<p>{{CommentContent}}</p>
|
||||
</div><!-- .comment-content -->
|
||||
|
||||
<div class="reply">
|
||||
{{respond}}
|
||||
</article><!-- .comment-body -->
|
||||
|
||||
`
|
||||
|
||||
var respondTml = `<div class="reply">
|
||||
<a rel="nofollow" class="comment-reply-link"
|
||||
href="/p/{{PostId}}?replytocom={{CommentId}}#respond" data-commentid="{{CommentId}}" data-postid="{{PostId}}"
|
||||
data-belowelement="div-comment-{{CommentId}}" data-respondelement="respond"
|
||||
data-replyto="回复给{{CommentAuthor}}"
|
||||
aria-label="回复给{{CommentAuthor}}">回复</a>
|
||||
</div>
|
||||
</article><!-- .comment-body -->
|
||||
</div>`
|
||||
|
||||
`
|
||||
|
||||
func FormatLi(li string, c *gin.Context, comments models.Comments, depth int, isTls bool, eo, parent string) string {
|
||||
func FormatLi(li string, comments models.Comments, respond RespondFn, currentDepth, maxDepth, page int, isTls, isThreadComments bool, eo, parent string) string {
|
||||
for k, v := range map[string]string{
|
||||
"{{CommentId}}": strconv.FormatUint(comments.CommentId, 10),
|
||||
"{{Depth}}": strconv.Itoa(depth),
|
||||
"{{Depth}}": strconv.Itoa(currentDepth),
|
||||
"{{Gravatar}}": Gravatar(comments.CommentAuthorEmail, isTls),
|
||||
"{{CommentAuthorUrl}}": comments.CommentAuthorUrl,
|
||||
"{{CommentAuthor}}": comments.CommentAuthor,
|
||||
"{{PostId}}": strconv.FormatUint(comments.CommentPostId, 10),
|
||||
"{{page}}": strconv.Itoa(page),
|
||||
"{{CommentDateGmt}}": comments.CommentDateGmt.String(),
|
||||
"{{CommentDate}}": comments.CommentDate.Format("2006-01-02 15:04"),
|
||||
"{{CommentContent}}": comments.CommentContent,
|
||||
"{{eo}}": eo,
|
||||
"{{parent}}": parent,
|
||||
"{{respond}}": respond(comments, currentDepth, maxDepth, isThreadComments),
|
||||
} {
|
||||
li = strings.Replace(li, k, v, -1)
|
||||
}
|
||||
|
141
app/plugins/devexample/plugintt/redisCache.go.dev
Normal file
141
app/plugins/devexample/plugintt/redisCache.go.dev
Normal 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")
|
||||
}
|
50
app/plugins/devexample/plugintt/redisCache.go.mod.dev
Normal file
50
app/plugins/devexample/plugintt/redisCache.go.mod.dev
Normal 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
|
||||
)
|
@ -3,35 +3,134 @@ package plugins
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/app/cmd/cachemanager"
|
||||
"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/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/maps"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"github.com/fthvgb1/wp-go/plugin/digest"
|
||||
"github.com/fthvgb1/wp-go/safety"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
var digestCache *cache.MapCache[uint64, string]
|
||||
|
||||
var more = regexp.MustCompile("<!--more(.*?)?-->")
|
||||
|
||||
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() {
|
||||
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 {
|
||||
return removeWpBlock.ReplaceAllString(s, "")
|
||||
}
|
||||
|
||||
func digestRaw(arg ...any) (string, error) {
|
||||
ctx := arg[0].(context.Context)
|
||||
func digestRaw(ctx context.Context, id uint64, arg ...any) (string, error) {
|
||||
s := arg[1].(string)
|
||||
id := arg[2].(uint64)
|
||||
limit := arg[3].(int)
|
||||
if limit < 0 {
|
||||
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 {
|
||||
closeTag := ""
|
||||
content = RemoveWpBlock(content)
|
||||
tag := config.GetConfig().DigestAllowTag
|
||||
c := digestConfig.Load()
|
||||
tag := c.DigestAllowTag
|
||||
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>"
|
||||
}
|
||||
content = digest.StripTags(content, tag)
|
||||
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 {
|
||||
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) {
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -2,8 +2,12 @@ package plugins
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
||||
"github.com/fthvgb1/wp-go/helper"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@ -18,6 +22,13 @@ type PageEle struct {
|
||||
func TwentyFifteenPagination() PageEle {
|
||||
return twentyFifteen
|
||||
}
|
||||
func TwentyFifteenCommentPagination() CommentPageEle {
|
||||
return twentyFifteenComment
|
||||
}
|
||||
|
||||
type CommentPageEle struct {
|
||||
PageEle
|
||||
}
|
||||
|
||||
var twentyFifteen = PageEle{
|
||||
PrevEle: `<a class="prev page-numbers" href="%s">上一页</a>`,
|
||||
@ -28,6 +39,12 @@ var twentyFifteen = PageEle{
|
||||
CurrentEle: `<span aria-current="page" class="page-numbers current">
|
||||
<span class="meta-nav screen-reader-text">页 </span>%d</span>`,
|
||||
}
|
||||
var twentyFifteenComment = CommentPageEle{
|
||||
PageEle{
|
||||
PrevEle: `<div class="nav-previous"><a href="%s">%s</a></div>`,
|
||||
NextEle: `<div class="nav-next"><a href="%s">%s</a></div>`,
|
||||
},
|
||||
}
|
||||
|
||||
func (p PageEle) Current(page, totalPage, totalRow int) string {
|
||||
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 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/") {
|
||||
path = fmt.Sprintf("%s%s", path, "/page/1")
|
||||
}
|
||||
@ -65,5 +96,81 @@ func (p PageEle) Url(path, query string, page int) string {
|
||||
if 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)
|
||||
}
|
||||
|
@ -2,12 +2,29 @@ package apply
|
||||
|
||||
import "github.com/fthvgb1/wp-go/safety"
|
||||
|
||||
var fn safety.Var[any]
|
||||
var contains = safety.NewMap[string, any]()
|
||||
|
||||
func SetFn(f any) {
|
||||
fn.Store(f)
|
||||
func SetVal(key string, val any) {
|
||||
contains.Store(key, val)
|
||||
}
|
||||
|
||||
func UsePlugins() any {
|
||||
return fn.Load()
|
||||
func DelVal(key string) {
|
||||
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
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ type Options struct {
|
||||
Linehover bool `json:"linehover,omitempty"`
|
||||
RawcodeDbclick bool `json:"rawcodeDbclick,omitempty"`
|
||||
TextOverflow string `json:"textOverflow,omitempty"`
|
||||
Linenumbers int64 `json:"linenumbers,omitempty"`
|
||||
Linenumbers bool `json:"linenumbers,omitempty"`
|
||||
Theme string `json:"theme,omitempty"`
|
||||
Language string `json:"language,omitempty"`
|
||||
RetainCssClasses bool `json:"retainCssClasses,omitempty"`
|
||||
@ -58,7 +58,7 @@ func EnlighterJS(h *wp.Handle) {
|
||||
Linehover: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-linehover", true),
|
||||
RawcodeDbclick: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-rawcodedbclick", true),
|
||||
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"),
|
||||
Language: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-language", "generic"),
|
||||
RetainCssClasses: maps.GetStrAnyValWithDefaults(opp, "enlighterjs-retaincss", false),
|
||||
|
@ -66,7 +66,7 @@ func LoadPlugins() {
|
||||
}
|
||||
RegisterPlugin(name, plu)
|
||||
}
|
||||
apply.SetFn(func(h *wp.Handle) {
|
||||
apply.SetVal("wp-plugins", func(h *wp.Handle) {
|
||||
UsePlugins(h)
|
||||
})
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
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{
|
||||
`<li><a href="/wp-login.php">登录</a></li>`: "",
|
||||
`<li><a href="/feed">登录</a></li>`: "",
|
||||
|
@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
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
|
||||
})
|
||||
/*h.PushPipeHook(constraints.Home, func(pipe wp.Pipe) (wp.Pipe, bool) {
|
||||
|
147
app/route/route.go
Normal file
147
app/route/route.go
Normal 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
|
||||
}
|
@ -3,6 +3,7 @@ package theme
|
||||
import (
|
||||
"embed"
|
||||
"github.com/fthvgb1/wp-go/multipTemplate"
|
||||
"github.com/fthvgb1/wp-go/safety"
|
||||
"html/template"
|
||||
"io/fs"
|
||||
"path/filepath"
|
||||
@ -12,20 +13,36 @@ import (
|
||||
//go:embed *[^.go]
|
||||
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 {
|
||||
t := multipTemplate.NewFsTemplate(TemplateFs)
|
||||
templates = t.Template
|
||||
t.FuncMap = FuncMap()
|
||||
commonTemplate(t)
|
||||
/*t.AddTemplate("twentyfifteen/*[^layout]/*.gohtml", FuncMap(), "twentyfifteen/layout/*.gohtml"). //单个主题设置
|
||||
AddTemplate("twentyseventeen/*[^layout]/*.gohtml", FuncMap(), "twentyseventeen/layout/*.gohtml")*/
|
||||
return t
|
||||
var multiple *multipTemplate.MultipleFsTemplate
|
||||
|
||||
func BuildTemplate() *multipTemplate.MultipleFsTemplate {
|
||||
if multiple != nil {
|
||||
tt := multipTemplate.NewFsTemplate(TemplateFs)
|
||||
commonTemplate(tt)
|
||||
for k, v := range map[string]*template.Template(any(tt.Template).(multipTemplate.TemplateMaps)) {
|
||||
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) {
|
||||
t, ok := templates[name]
|
||||
t, ok := templates.Load(name)
|
||||
return t, ok
|
||||
}
|
||||
|
||||
@ -35,10 +52,11 @@ func commonTemplate(t *multipTemplate.MultipleFsTemplate) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
funMap := FuncMap()
|
||||
for _, main := range m {
|
||||
file := filepath.Base(main)
|
||||
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)
|
||||
}
|
||||
}
|
||||
@ -55,6 +73,10 @@ func IsTemplateDirExists(tml string) bool {
|
||||
}
|
||||
|
||||
func IsTemplateExists(tml string) bool {
|
||||
t, ok := templates[tml]
|
||||
t, ok := templates.Load(tml)
|
||||
return ok && t != nil
|
||||
}
|
||||
|
||||
func SetTemplate(name string, val *template.Template) {
|
||||
templates.Store(name, val)
|
||||
}
|
||||
|
@ -1,25 +1,35 @@
|
||||
package theme
|
||||
|
||||
import (
|
||||
"github.com/fthvgb1/wp-go/app/theme/twentyfifteen"
|
||||
"github.com/fthvgb1/wp-go/app/theme/wp"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"github.com/fthvgb1/wp-go/safety"
|
||||
)
|
||||
|
||||
var themeMap = safety.NewMap[string, func(*wp.Handle)]()
|
||||
|
||||
func AddThemeHookFunc(name string, fn func(handle *wp.Handle)) {
|
||||
if _, ok := themeMap.Load(name); ok {
|
||||
panic("exists same name theme")
|
||||
}
|
||||
func AddTheme(name string, fn func(handle *wp.Handle)) {
|
||||
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) {
|
||||
fn, ok := themeMap.Load(themeName)
|
||||
if ok && fn != nil {
|
||||
fn(h)
|
||||
return
|
||||
}
|
||||
twentyfifteen.Hook(h)
|
||||
panic(str.Join("theme ", themeName, " don't exist"))
|
||||
}
|
||||
|
@ -7,7 +7,12 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
var comFn = template.FuncMap{
|
||||
func postsFn(fn func(models.Posts) string, a models.Posts) string {
|
||||
return fn(a)
|
||||
}
|
||||
|
||||
func FuncMap() template.FuncMap {
|
||||
return template.FuncMap{
|
||||
"unescaped": func(s string) any {
|
||||
return template.HTML(s)
|
||||
},
|
||||
@ -25,19 +30,8 @@ var comFn = template.FuncMap{
|
||||
"exec": func(fn func() string) template.HTML {
|
||||
return template.HTML(fn())
|
||||
},
|
||||
}
|
||||
|
||||
func postsFn(fn func(models.Posts) string, a models.Posts) string {
|
||||
return fn(a)
|
||||
}
|
||||
|
||||
func FuncMap() template.FuncMap {
|
||||
return comFn
|
||||
}
|
||||
|
||||
func addTemplateFunc(fnName string, fn any) {
|
||||
if _, ok := comFn[fnName]; ok {
|
||||
panic("exists same name func")
|
||||
"callFuncString": func(fn func(string) string, s string) template.HTML {
|
||||
return template.HTML(fn(s))
|
||||
},
|
||||
}
|
||||
comFn[fnName] = fn
|
||||
}
|
||||
|
@ -8,17 +8,17 @@ import (
|
||||
)
|
||||
|
||||
func InitTheme() {
|
||||
AddThemeHookFunc(twentyfifteen.ThemeName, twentyfifteen.Hook)
|
||||
AddThemeHookFunc(twentyseventeen.ThemeName, twentyseventeen.Hook)
|
||||
AddTheme(twentyfifteen.ThemeName, twentyfifteen.Hook)
|
||||
AddTheme(twentyseventeen.ThemeName, twentyseventeen.Hook)
|
||||
}
|
||||
|
||||
func GetCurrentTemplateName() string {
|
||||
tmlp := config.GetConfig().Theme
|
||||
if tmlp == "" {
|
||||
tmlp = wpconfig.GetOption("template")
|
||||
func GetCurrentTheme() string {
|
||||
themeName := config.GetConfig().Theme
|
||||
if themeName == "" {
|
||||
themeName = wpconfig.GetOption("template")
|
||||
}
|
||||
if !IsTemplateDirExists(tmlp) {
|
||||
tmlp = "twentyfifteen"
|
||||
if !IsTemplateDirExists(themeName) {
|
||||
themeName = "twentyfifteen"
|
||||
}
|
||||
return tmlp
|
||||
return themeName
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
package twentyfifteen
|
||||
|
||||
import (
|
||||
"github.com/fthvgb1/wp-go/app/cmd/reload"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/constraints"
|
||||
"github.com/fthvgb1/wp-go/app/theme/wp"
|
||||
"github.com/fthvgb1/wp-go/cache/reload"
|
||||
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) {
|
||||
img, rand := h.GetCustomHeaderImg()
|
||||
|
@ -31,11 +31,7 @@
|
||||
|
||||
{{end}}
|
||||
</div>
|
||||
<footer id="colophon" class="site-footer">
|
||||
<div class="site-info">
|
||||
<a href="https://cn.wordpress.org/" class="imprint">自豪地采用WordPress</a>
|
||||
</div>
|
||||
</footer>
|
||||
{{template "common/colophon" .}}
|
||||
|
||||
</div>
|
||||
{{template "layout/footer" .}}
|
||||
|
@ -52,14 +52,30 @@
|
||||
|
||||
{{ if .showComment}}
|
||||
<div id="comments" class="comments-area">
|
||||
{{ if gt .post.CommentCount 0}}
|
||||
<h2 class="comments-title">《{{.post.PostTitle}}》上有{{.post.CommentCount}}条评论 </h2>
|
||||
{{ if ne .comments ""}}
|
||||
<h2 class="comments-title">《{{.post.PostTitle}}》上有{{.totalCommentNum}}条评论 </h2>
|
||||
{{if gt .totalCommentPage 1}}
|
||||
<nav class="navigation comment-navigation">
|
||||
<h2 class="screen-reader-text">评论导航</h2>
|
||||
<div class="nav-links">
|
||||
{{ .commentPageNav|unescaped}}
|
||||
</div><!-- .nav-links -->
|
||||
</nav>
|
||||
{{end}}
|
||||
<ol class="comment-list">
|
||||
{{.comments|unescaped}}
|
||||
</ol>
|
||||
{{if gt .totalCommentPage 1}}
|
||||
<nav class="navigation comment-navigation">
|
||||
<h2 class="screen-reader-text">评论导航</h2>
|
||||
<div class="nav-links">
|
||||
{{ .commentPageNav|unescaped}}
|
||||
</div><!-- .nav-links -->
|
||||
</nav>
|
||||
{{end}}
|
||||
|
||||
{{end}}
|
||||
{{if and (eq .post.CommentStatus "open") (eq ( "thread_comments" |getOption ) "1")}}
|
||||
{{if eq .post.CommentStatus "open"}}
|
||||
<div id="respond" class="comment-respond">
|
||||
<h3 id="reply-title" class="comment-reply-title">发表回复
|
||||
<small>
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
"github.com/fthvgb1/wp-go/app/plugins"
|
||||
"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/widget"
|
||||
"github.com/fthvgb1/wp-go/app/theme/wp/middleware"
|
||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
||||
"strings"
|
||||
)
|
||||
@ -18,20 +18,17 @@ func Hook(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"`)
|
||||
})
|
||||
wp.InitPipe(h)
|
||||
h.PushHandler(constraints.PipeMiddleware, constraints.Home,
|
||||
wp.NewHandleFn(widget.IsCategory, 100, "widget.IsCategory"))
|
||||
|
||||
h.Index.SetPageEle(plugins.TwentyFifteenPagination())
|
||||
middleware.CommonMiddleware(h)
|
||||
setPaginationAndRender(h)
|
||||
h.PushCacheGroupHeadScript(constraints.AllScene, "CalCustomBackGround", 10.005, CalCustomBackGround)
|
||||
h.PushCacheGroupHeadScript(constraints.AllScene, "colorSchemeCss", 10.0056, colorSchemeCss)
|
||||
h.CommonComponents()
|
||||
components.WidgetArea(h)
|
||||
wp.ReplyCommentJs(h)
|
||||
h.SetData("customHeader", customHeader(h))
|
||||
h.PushRender(constraints.AllScene, wp.NewHandleFn(renderCustomHeader, 20.5, "renderCustomHeader"))
|
||||
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(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"))
|
||||
}
|
||||
|
||||
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) {
|
||||
if h.Detail.Post.Thumbnail.Path != "" {
|
||||
h.Detail.Post.Thumbnail = wpconfig.Thumbnail(h.Detail.Post.Thumbnail.OriginAttachmentData, "post-thumbnail", "")
|
||||
d := h.GetDetailHandle()
|
||||
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))
|
||||
}
|
||||
|
@ -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}}
|
@ -64,12 +64,21 @@
|
||||
|
||||
{{ if .showComment}}
|
||||
<div id="comments" class="comments-area">
|
||||
{{ if gt .post.CommentCount 0}}
|
||||
<h2 class="comments-title">“{{.post.PostTitle}}”的{{.post.CommentCount}}个回复 </h2>
|
||||
{{ if ne .comments ""}}
|
||||
<h2 class="comments-title">“{{.post.PostTitle}}”的{{.totalCommentNum}}个回复 </h2>
|
||||
<ol class="comment-list">
|
||||
{{.comments|unescaped}}
|
||||
</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}}
|
||||
{{if eq .post.CommentStatus "open"}}
|
||||
{{template "respond" .}}
|
||||
@ -123,7 +132,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{template "layout/colophon"}}
|
||||
{{template "common/colophon" .}}
|
||||
|
||||
</div>
|
||||
|
||||
|
@ -77,6 +77,6 @@
|
||||
{{end}}
|
||||
|
||||
</div>
|
||||
{{template "layout/colophon"}}
|
||||
{{template "common/colophon" .}}
|
||||
</div>
|
||||
{{end}}
|
@ -1,8 +1,8 @@
|
||||
package twentyseventeen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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/widgets"
|
||||
"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/theme/wp"
|
||||
"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/cache/reload"
|
||||
"github.com/fthvgb1/wp-go/helper"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/fthvgb1/wp-go/plugin/pagination"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@ -27,23 +28,33 @@ var paginate = func() plugins.PageEle {
|
||||
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>`, 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
|
||||
}()
|
||||
|
||||
var commentPageEle pagination.Render
|
||||
|
||||
func Hook(h *wp.Handle) {
|
||||
wp.Run(h, configs)
|
||||
}
|
||||
|
||||
func configs(h *wp.Handle) {
|
||||
wp.InitPipe(h)
|
||||
h.PushHandler(constraints.PipeMiddleware, constraints.Home,
|
||||
wp.NewHandleFn(widget.IsCategory, 100.006, "widget.IsCategory"))
|
||||
h.PushComponentFilterFn("bodyClass", calClass)
|
||||
middleware.CommonMiddleware(h)
|
||||
h.AddActionFilter("bodyClass", calClass)
|
||||
h.PushCacheGroupHeadScript(constraints.AllScene, "colorScheme-customHeader", 10, colorScheme, customHeader)
|
||||
components.WidgetArea(h)
|
||||
pushScripts(h)
|
||||
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">`,
|
||||
"{$after_widget}": `</section>`,
|
||||
})
|
||||
@ -52,12 +63,11 @@ func configs(h *wp.Handle) {
|
||||
wp.NewHandleFn(errorsHandle, 80.005, "errorsHandle"),
|
||||
)
|
||||
videoHeader(h)
|
||||
h.Detail.CommentRender = commentFormat
|
||||
h.SetData("colophon", colophon)
|
||||
setPaginationAndRender(h)
|
||||
h.CommonComponents()
|
||||
h.Index.SetPageEle(paginate)
|
||||
wp.ReplyCommentJs(h)
|
||||
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"))
|
||||
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"))
|
||||
@ -65,6 +75,19 @@ func configs(h *wp.Handle) {
|
||||
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="/">
|
||||
<label for="search-form-1">
|
||||
<span class="screen-reader-text">{$label}:</span>
|
||||
@ -85,7 +108,7 @@ func errorsHandle(h *wp.Handle) {
|
||||
}
|
||||
|
||||
func postThumb(h *wp.Handle) {
|
||||
d := h.Detail
|
||||
d := h.GetDetailHandle()
|
||||
if d.Post.Thumbnail.Path != "" {
|
||||
img := wpconfig.Thumbnail(d.Post.Thumbnail.OriginAttachmentData, "full", "", "thumbnail", "post-thumbnail")
|
||||
img.Sizes = "100vw"
|
||||
@ -100,20 +123,28 @@ type comment struct {
|
||||
plugins.CommonCommentFormat
|
||||
}
|
||||
|
||||
func (c comment) FormatLi(ctx *gin.Context, m models.Comments, depth int, isTls bool, eo, parent string) string {
|
||||
templ := plugins.CommonLi()
|
||||
templ = strings.ReplaceAll(templ, `<a rel="nofollow" class="comment-reply-link"
|
||||
href="/p/{{PostId}}?replytocom={{CommentId}}#respond" data-commentid="{{CommentId}}" data-postid="{{PostId}}"
|
||||
data-belowelement="div-comment-{{CommentId}}" data-respondelement="respond"
|
||||
data-replyto="回复给{{CommentAuthor}}"
|
||||
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 commentLi = plugins.CommonLi()
|
||||
|
||||
var respondFn = plugins.Responds(respondStr)
|
||||
|
||||
func (c comment) FormatLi(_ context.Context, m models.Comments, depth, maxDepth, page int, isTls, isThreadComments bool, eo, parent string) string {
|
||||
return plugins.FormatLi(commentLi, m, respondFn, depth, maxDepth, page, isTls, isThreadComments, 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) {
|
||||
if posts.Thumbnail.Path != "" {
|
||||
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) {
|
||||
h.SetData("HeaderImage", getHeaderImage(h))
|
||||
@ -176,7 +207,7 @@ func calClass(h *wp.Handle, s string, _ ...any) string {
|
||||
}
|
||||
|
||||
func videoHeader(h *wp.Handle) {
|
||||
h.PushComponentFilterFn("videoSetting", videoPlay)
|
||||
h.AddActionFilter("videoSetting", videoPlay)
|
||||
wp.CustomVideo(h, constraints.Home)
|
||||
}
|
||||
|
||||
|
@ -33,14 +33,14 @@ func (h *Handle) BodyClass() string {
|
||||
|
||||
case constraints.Search:
|
||||
s := "search-no-results"
|
||||
if len(h.Index.Posts) > 0 {
|
||||
if len(h.GetIndexHandle().Posts) > 0 {
|
||||
s = "search-results"
|
||||
}
|
||||
class = append(class, "search", s)
|
||||
|
||||
case constraints.Category, constraints.Tag:
|
||||
class = append(class, "archive", "category")
|
||||
cat := h.Index.Param.Category
|
||||
cat := h.GetIndexHandle().Param.Category
|
||||
if cat == "" {
|
||||
break
|
||||
}
|
||||
@ -54,16 +54,16 @@ func (h *Handle) BodyClass() string {
|
||||
|
||||
case constraints.Author:
|
||||
class = append(class, "archive", "author")
|
||||
author := h.Index.Param.Author
|
||||
author := h.GetIndexHandle().Param.Author
|
||||
user, _ := cache.GetUserByName(h.C, author)
|
||||
class = append(class, str.Join("author-", number.IntToString(user.Id)))
|
||||
if user.UserLogin[0] != '%' {
|
||||
class = append(class, str.Join("author-", user.UserLogin))
|
||||
if user.DisplayName[0] != '%' {
|
||||
class = append(class, str.Join("author-", user.DisplayName))
|
||||
}
|
||||
|
||||
case constraints.Detail:
|
||||
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 {
|
||||
class = append(class, "single-format-standard")
|
||||
}
|
||||
@ -77,7 +77,7 @@ func (h *Handle) BodyClass() string {
|
||||
if h.themeMods.ThemeSupport.ResponsiveEmbeds {
|
||||
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 {
|
||||
@ -113,7 +113,7 @@ func (h *Handle) PostClass(posts models.Posts) string {
|
||||
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 {
|
||||
|
126
app/theme/wp/comments.go
Normal file
126
app/theme/wp/comments.go
Normal 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
|
||||
}
|
@ -1,62 +1,103 @@
|
||||
package wp
|
||||
|
||||
import (
|
||||
"github.com/fthvgb1/wp-go/app/cmd/reload"
|
||||
"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/slice"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"github.com/fthvgb1/wp-go/safety"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (h *Handle) DeleteComponents(scene, name string) {
|
||||
h.componentHook[scene] = append(h.componentHook[scene], func(c Components[string]) (Components[string], bool) {
|
||||
return c, c.Name != name
|
||||
var handleComponents = safety.NewMap[string, map[string][]Components[string]]()
|
||||
var handleComponentHook = safety.NewMap[string, map[string][]func(Components[string]) (Components[string], bool)]()
|
||||
|
||||
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]) {
|
||||
h.componentHook[scene] = append(h.componentHook[scene], func(c Components[string]) (Components[string], bool) {
|
||||
if c.Name == name {
|
||||
func (h *Handle) ReplaceComponents(scene, componentKey, componentName string, components Components[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) {
|
||||
if c.Name == componentName {
|
||||
c = components
|
||||
}
|
||||
return c, true
|
||||
})
|
||||
handleComponentHook.Store(scene, v)
|
||||
}
|
||||
func (h *Handle) HookComponents(scene string, fn func(Components[string]) (Components[string], bool)) {
|
||||
h.componentHook[scene] = append(h.componentHook[scene], fn)
|
||||
func (h *Handle) PushComponentHooks(scene, componentKey string, fn func(Components[string]) (Components[string], bool)) {
|
||||
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) {
|
||||
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) {
|
||||
vv := append(v1, v2...)
|
||||
return vv, vv != nil
|
||||
}, nil, h.components[h.scene], h.components[constraints.AllScene])
|
||||
})
|
||||
for k, components := range componentss {
|
||||
key := str.Join("calComponents-", h.scene, "-", k)
|
||||
ss := reload.GetAnyValMapBy("calComponents", key, h, func(h *Handle) []Components[string] {
|
||||
r := slice.FilterAndMap(components, func(t Components[string]) (Components[string], bool) {
|
||||
fns, ok := h.componentHook[k]
|
||||
var GetAndHookComponents = reload.BuildMapFnWithAnyParams[string]("calComponents", HookComponent)
|
||||
|
||||
func HookComponent(a ...any) []Components[string] {
|
||||
componentKey := a[0].(string)
|
||||
scene := a[1].(string)
|
||||
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 t, true
|
||||
return component, true
|
||||
}
|
||||
for _, fn := range fns {
|
||||
c, ok := fn(t)
|
||||
if !ok {
|
||||
return c, false
|
||||
hooks := keyHooks[componentKey]
|
||||
for _, fn := range hooks {
|
||||
hookedComponent, ok := fn(component)
|
||||
if !ok { // DeleteComponents fn
|
||||
return hookedComponent, false
|
||||
}
|
||||
t = c
|
||||
component = hookedComponent // ReplaceComponents fn
|
||||
}
|
||||
return t, true
|
||||
return component, true
|
||||
})
|
||||
slice.Sort(r, func(i, j Components[string]) bool {
|
||||
return i.Order > j.Order
|
||||
slice.SimpleSort(r, slice.DESC, func(t Components[string]) float64 {
|
||||
return t.Order
|
||||
})
|
||||
return r
|
||||
})
|
||||
var s = make([]string, 0, len(ss))
|
||||
for _, component := range ss {
|
||||
}
|
||||
|
||||
func GetComponents(scene, key string) (r []Components[string]) {
|
||||
sceneComponents, _ := handleComponents.Load(scene)
|
||||
allSceneComponents, _ := handleComponents.Load(constraints.AllScene)
|
||||
r = append(sceneComponents[key], allSceneComponents[key]...)
|
||||
return
|
||||
}
|
||||
|
||||
var CacheComponent = reload.BuildMapFnWithAnyParams[string]("cacheComponents", cacheComponentFn)
|
||||
|
||||
func cacheComponentFn(a ...any) string {
|
||||
return a[0].(Components[string]).Fn(a[1].(*Handle))
|
||||
}
|
||||
|
||||
func CalComponent(h *Handle) func(string) string {
|
||||
return func(componentKey string) string {
|
||||
cacheKey := str.Join("get-hook-Components-", h.scene, "-", componentKey)
|
||||
hookedComponents := GetAndHookComponents(cacheKey, componentKey, h.scene)
|
||||
var s = make([]string, 0, len(hookedComponents))
|
||||
for _, component := range hookedComponents {
|
||||
if component.Val != "" {
|
||||
s = append(s, component.Val)
|
||||
continue
|
||||
@ -64,7 +105,8 @@ func CalComponents(h *Handle) {
|
||||
if component.Fn != nil {
|
||||
v := ""
|
||||
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 {
|
||||
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]) {
|
||||
c, ok := h.components[scene]
|
||||
c, ok := handleComponents.Load(scene)
|
||||
if !ok {
|
||||
c = make(map[string][]Components[string])
|
||||
h.components[scene] = c
|
||||
}
|
||||
c[componentType] = append(c[componentType], components...)
|
||||
handleComponents.Store(scene, c)
|
||||
}
|
||||
|
||||
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...)
|
||||
}
|
||||
|
||||
func GetComponentsArgs[T any](h *Handle, k string, defaults T) T {
|
||||
v, ok := h.componentsArgs[k]
|
||||
func GetComponentsArgs[T any](k string, defaults T) T {
|
||||
v, ok := componentsArgs.Load(k)
|
||||
if ok {
|
||||
vv, ok := v.(T)
|
||||
if ok {
|
||||
@ -150,60 +192,61 @@ func GetComponentsArgs[T any](h *Handle, k string, defaults T) T {
|
||||
return defaults
|
||||
}
|
||||
|
||||
func PushComponentsArgsForSlice[T any](h *Handle, name string, v ...T) {
|
||||
val, ok := h.componentsArgs[name]
|
||||
func PushComponentsArgsForSlice[T any](name string, v ...T) {
|
||||
val, ok := componentsArgs.Load(name)
|
||||
if !ok {
|
||||
var vv []T
|
||||
vv = append(vv, v...)
|
||||
h.componentsArgs[name] = vv
|
||||
componentsArgs.Store(name, vv)
|
||||
return
|
||||
}
|
||||
vv, ok := val.([]T)
|
||||
if ok {
|
||||
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) {
|
||||
val, ok := h.componentsArgs[name]
|
||||
func SetComponentsArgsForMap[K comparable, V any](name string, key K, v V) {
|
||||
val, ok := componentsArgs.Load(name)
|
||||
if !ok {
|
||||
vv := make(map[K]V)
|
||||
vv[key] = v
|
||||
h.componentsArgs[name] = vv
|
||||
componentsArgs.Store(name, vv)
|
||||
return
|
||||
}
|
||||
vv, ok := val.(map[K]V)
|
||||
if ok {
|
||||
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) {
|
||||
val, ok := h.componentsArgs[name]
|
||||
func MergeComponentsArgsForMap[K comparable, V any](name string, m map[K]V) {
|
||||
val, ok := componentsArgs.Load(name)
|
||||
if !ok {
|
||||
h.componentsArgs[name] = m
|
||||
componentsArgs.Store(name, m)
|
||||
return
|
||||
}
|
||||
vv, ok := val.(map[K]V)
|
||||
if ok {
|
||||
h.componentsArgs[name] = maps.Merge(vv, m)
|
||||
componentsArgs.Store(name, maps.Merge(vv, m))
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handle) SetComponentsArgs(key string, value any) {
|
||||
h.componentsArgs[key] = value
|
||||
func SetComponentsArgs(key string, value any) {
|
||||
componentsArgs.Store(key, value)
|
||||
}
|
||||
|
||||
func (h *Handle) ComponentFilterFn(name string) ([]func(*Handle, string, ...any) string, bool) {
|
||||
fn, ok := h.componentFilterFn[name]
|
||||
return fn, ok
|
||||
func (h *Handle) GetComponentFilterFn(name string) ([]func(*Handle, string, ...any) string, bool) {
|
||||
return componentFilterFns.Load(name)
|
||||
}
|
||||
|
||||
func (h *Handle) PushComponentFilterFn(name string, fns ...func(*Handle, string, ...any) string) {
|
||||
h.componentFilterFn[name] = append(h.componentFilterFn[name], fns...)
|
||||
func (h *Handle) AddActionFilter(name string, fns ...func(*Handle, string, ...any) string) {
|
||||
v, _ := componentFilterFns.Load(name)
|
||||
v = append(v, fns...)
|
||||
componentFilterFns.Store(name, v)
|
||||
}
|
||||
func (h *Handle) ComponentFilterFnHook(name, s string, args ...any) string {
|
||||
calls, ok := h.componentFilterFn[name]
|
||||
func (h *Handle) DoActionFilter(name, s string, args ...any) string {
|
||||
calls, ok := componentFilterFns.Load(name)
|
||||
if ok {
|
||||
return slice.Reduce(calls, func(fn func(*Handle, string, ...any) string, r string) string {
|
||||
return fn(h, r, args...)
|
||||
|
@ -1,6 +1,7 @@
|
||||
package components
|
||||
|
||||
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/components/block"
|
||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
||||
@ -29,6 +30,7 @@ func Block(id string) (func(*wp.Handle) string, string) {
|
||||
if ok {
|
||||
s, err := fn(h, id, parserBlock)
|
||||
if err != nil {
|
||||
logs.Error(err, str.Join("parse block", parserBlock.Name, " fail "), parserBlock)
|
||||
continue
|
||||
}
|
||||
out = append(out, s())
|
||||
|
@ -4,7 +4,6 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/app/cmd/reload"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/cache"
|
||||
constraints2 "github.com/fthvgb1/wp-go/app/pkg/constraints"
|
||||
"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/theme/wp"
|
||||
"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/number"
|
||||
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")
|
||||
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)
|
||||
if !ok {
|
||||
return "", "", false
|
||||
@ -71,15 +71,16 @@ func parseAttr(attr map[any]any) string {
|
||||
return strings.Join(attrs, " ")
|
||||
}
|
||||
|
||||
func Category(h *wp.Handle, id string, blockParser ParserBlock) (func() string, error) {
|
||||
counter := number.Counters[int]()
|
||||
var err error
|
||||
conf := reload.GetAnyValBys("block-category-conf", h, func(h *wp.Handle) map[any]any {
|
||||
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)
|
||||
err := json.Unmarshal([]byte(blockParser.Attrs), &con)
|
||||
if err != nil {
|
||||
logs.Error(err, "解析category attr错误", blockParser.Attrs)
|
||||
return nil
|
||||
return nil, false
|
||||
}
|
||||
var conf map[any]any
|
||||
switch con.(type) {
|
||||
@ -111,8 +112,20 @@ func Category(h *wp.Handle, id string, blockParser ParserBlock) (func() string,
|
||||
classes = append(classes, "wp-block-categories-list")
|
||||
conf["className"] = strings.Join(classes, " ")
|
||||
}
|
||||
return conf
|
||||
})
|
||||
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) {
|
||||
counter := number.Counters[int]()
|
||||
var err error
|
||||
conf := GetCategoryConf(blockParser)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -127,10 +140,7 @@ func Category(h *wp.Handle, id string, blockParser ParserBlock) (func() string,
|
||||
if maps.GetAnyAnyValWithDefaults(conf, false, "showOnlyTopLevel") {
|
||||
h.C.Set("showOnlyTopLevel", true)
|
||||
}
|
||||
args := reload.GetAnyValBys("block-category-args", h, func(h *wp.Handle) map[string]string {
|
||||
args := wp.GetComponentsArgs(h, widgets.Widget, map[string]string{})
|
||||
return maps.FilterZeroMerge(categoryDefaultArgs(), args)
|
||||
})
|
||||
args := GetCategoryArgs()
|
||||
|
||||
return func() string {
|
||||
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}"])
|
||||
}
|
||||
|
||||
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()
|
||||
li := widget.CategoryLi(h, conf, categories)
|
||||
attrs := reload.GetAnyValBys("block-category-attr", conf, parseAttr)
|
||||
li := widget.CategoryLi(h, confAttr, categories)
|
||||
attrs := GetCategoryAttr(confAttr)
|
||||
s.Sprintf(`<ul %s>%s</ul>`, attrs, li)
|
||||
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()
|
||||
ids := fmt.Sprintf(`wp-block-categories-%v`, id)
|
||||
args = maps.Copy(args)
|
||||
args["{$selectId}"] = ids
|
||||
attrs := reload.GetAnyValBys("block-category-attr", conf, parseAttr)
|
||||
selects := widget.DropdownCategories(h, args, conf, categories)
|
||||
attrs := GetCategoryAttr(confAttr)
|
||||
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))
|
||||
return s.String()
|
||||
}
|
||||
|
@ -2,11 +2,11 @@ package widget
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/app/cmd/reload"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/cache"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/constraints/widgets"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
||||
"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/slice"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
@ -40,12 +40,16 @@ var archivesConfig = map[any]any{
|
||||
"title": "归档",
|
||||
}
|
||||
|
||||
func Archive(h *wp.Handle, id string) string {
|
||||
conf := configs(archivesConfig, "widget_archives", int64(2))
|
||||
args := reload.GetAnyValBys("widget-archive-args", h, func(h *wp.Handle) map[string]string {
|
||||
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(h, widgets.Widget, CommonArgs())
|
||||
args := wp.GetComponentsArgs(h, widgets.Archive, 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}"])
|
||||
@ -54,7 +58,11 @@ func Archive(h *wp.Handle, id string) string {
|
||||
args["{$navCloser}"] = "</nav>"
|
||||
}
|
||||
return args
|
||||
})
|
||||
}
|
||||
|
||||
func Archive(h *wp.Handle, id string) string {
|
||||
conf := GetArchiveConf()
|
||||
args := GetArchiveArgs(h, conf, id)
|
||||
|
||||
s := archiveTemplate
|
||||
if int64(1) == conf["dropdown"].(int64) {
|
||||
@ -62,7 +70,7 @@ func Archive(h *wp.Handle, id string) string {
|
||||
} else {
|
||||
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 = `
|
||||
@ -83,11 +91,12 @@ var dropdownScript = `
|
||||
func archiveDropDown(h *wp.Handle, conf map[any]any, args map[string]string, archives []models.PostArchive) string {
|
||||
option := str.NewBuilder()
|
||||
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)
|
||||
for _, archive := range archives {
|
||||
sel := ""
|
||||
if h.Index.Param.Year == archive.Year && month == archive.Month {
|
||||
if i.Param.Year == archive.Year && month == archive.Month {
|
||||
sel = "selected"
|
||||
}
|
||||
count := ""
|
||||
|
@ -2,17 +2,16 @@ package widget
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/app/cmd/reload"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/cache"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/constraints"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/constraints/widgets"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
||||
"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/slice"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"github.com/fthvgb1/wp-go/helper/tree"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@ -45,12 +44,16 @@ func categoryArgs() map[string]string {
|
||||
}
|
||||
}
|
||||
|
||||
func Category(h *wp.Handle, id string) string {
|
||||
conf := configs(categoryConfig, "widget_categories", int64(2))
|
||||
var GetCategoryConf = BuildconfigFn(categoryConfig, "widget_categories", int64(2))
|
||||
|
||||
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())
|
||||
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}"])
|
||||
@ -59,8 +62,11 @@ func Category(h *wp.Handle, id string) string {
|
||||
args["{$navCloser}"] = "</nav>"
|
||||
}
|
||||
return args
|
||||
})
|
||||
}
|
||||
|
||||
func Category(h *wp.Handle, id string) string {
|
||||
conf := GetCategoryConf()
|
||||
args := GetCategoryArgs(h, conf, id)
|
||||
t := categoryTemplate
|
||||
dropdown := conf["dropdown"].(int64)
|
||||
categories := cache.CategoriesTags(h.C, constraints.Category)
|
||||
@ -69,7 +75,7 @@ func Category(h *wp.Handle, id string) string {
|
||||
} else {
|
||||
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 {
|
||||
@ -202,8 +208,9 @@ func DropdownCategories(h *wp.Handle, args map[string]string, conf map[any]any,
|
||||
s.Sprintf(` <option value="-1">%s</option>
|
||||
`, args["{$show_option_none}"])
|
||||
currentCategory := ""
|
||||
i := h.GetIndexHandle()
|
||||
if h.Scene() == constraints.Category {
|
||||
currentCategory = h.Index.Param.Category
|
||||
currentCategory = i.Param.Category
|
||||
}
|
||||
showCount := conf["count"].(int64)
|
||||
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")
|
||||
return h.ComponentFilterFnHook("wp_dropdown_cats", s.String())
|
||||
return h.DoActionFilter("wp_dropdown_cats", s.String())
|
||||
}
|
||||
|
||||
func IsCategory(h *wp.Handle) {
|
||||
name, ok := parseDropdownCate(h)
|
||||
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())
|
||||
func IsCategory(h *wp.Handle) (category models.TermsMy, r bool) {
|
||||
cate := wp.GetComponentsArgs[map[string]string](widgets.Categories, categoryArgs())
|
||||
name, ok := cate["{$name}"]
|
||||
if !ok || name == "" {
|
||||
return
|
||||
@ -264,6 +263,15 @@ func parseDropdownCate(h *wp.Handle) (cateName string, r bool) {
|
||||
return
|
||||
}
|
||||
r = true
|
||||
cateName = cc.Name
|
||||
category = cc
|
||||
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 ""
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
package widget
|
||||
|
||||
import (
|
||||
"github.com/fthvgb1/wp-go/app/cmd/reload"
|
||||
"github.com/fthvgb1/wp-go/app/theme/wp"
|
||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
||||
"github.com/fthvgb1/wp-go/cache/reload"
|
||||
"github.com/fthvgb1/wp-go/helper/maps"
|
||||
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 {
|
||||
return reload.GetAnyValBys(str.Join("widget-config-", key), key, func(_ string) M {
|
||||
c := wpconfig.GetPHPArrayVal[M](key, nil, a...)
|
||||
func configFns[K comparable, V any](m map[K]V, key string, a ...any) func(_ ...any) map[K]V {
|
||||
return func(_ ...any) map[K]V {
|
||||
c := wpconfig.GetPHPArrayVal[map[K]V](key, nil, a...)
|
||||
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...))
|
||||
}
|
||||
|
@ -2,10 +2,10 @@ package widget
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/app/cmd/reload"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/constraints/widgets"
|
||||
"github.com/fthvgb1/wp-go/app/theme/wp"
|
||||
"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/slice"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
@ -21,18 +21,21 @@ var metaTemplate = `{$before_widget}
|
||||
{$navCloser}
|
||||
{$after_widget}`
|
||||
|
||||
func metaArgs() map[string]string {
|
||||
func defaultMetaArgs() map[string]string {
|
||||
return map[string]string{
|
||||
"{$aria_label}": "",
|
||||
"{$title}": "",
|
||||
}
|
||||
}
|
||||
|
||||
func Meta(h *wp.Handle, id string) string {
|
||||
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)
|
||||
var GetMetaArgs = reload.BuildValFnWithAnyParams("widget-meta-args", ParseMetaArgs)
|
||||
|
||||
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")
|
||||
@ -47,8 +50,10 @@ func Meta(h *wp.Handle, id string) string {
|
||||
args["{$navCloser}"] = "</nav>"
|
||||
}
|
||||
return args
|
||||
})
|
||||
}
|
||||
|
||||
func Meta(h *wp.Handle, id string) string {
|
||||
args := GetMetaArgs(h, id)
|
||||
ss := str.NewBuilder()
|
||||
if str.ToInteger(wpconfig.GetOption("users_can_register"), 0) > 0 {
|
||||
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>`, "/comments/feed")
|
||||
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))
|
||||
}
|
||||
|
@ -2,11 +2,11 @@ package widget
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/app/cmd/reload"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/cache"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/constraints/widgets"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
||||
"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/slice"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
@ -39,13 +39,17 @@ var recentCommentsTemplate = `{$before_widget}
|
||||
{$after_widget}
|
||||
`
|
||||
|
||||
func RecentComments(h *wp.Handle, id string) string {
|
||||
conf := configs(recentCommentConf, "widget_recent-comments", int64(2))
|
||||
var GetRecentCommentConf = BuildconfigFn(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)
|
||||
|
||||
func RecentCommentArgs(a ...any) map[string]string {
|
||||
h := a[0].(*wp.Handle)
|
||||
conf := a[1].(map[any]any)
|
||||
id := a[2].(string)
|
||||
commentsArgs := recentCommentsArgs()
|
||||
commonArgs := wp.GetComponentsArgs(h, widgets.Widget, map[string]string{})
|
||||
args := wp.GetComponentsArgs(h, widgets.RecentComments, commentsArgs)
|
||||
commonArgs := wp.GetComponentsArgs(widgets.Widget, map[string]string{})
|
||||
args := wp.GetComponentsArgs(widgets.RecentComments, commentsArgs)
|
||||
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"))
|
||||
args["{$title}"] = str.Join(args["{$before_title}"], conf["title"].(string), args["{$after_title}"])
|
||||
@ -54,15 +58,18 @@ func RecentComments(h *wp.Handle, id string) string {
|
||||
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 {
|
||||
return fmt.Sprintf(` <li>
|
||||
<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"))
|
||||
return h.ComponentFilterFnHook(widgets.RecentComments, str.Replace(s, args))
|
||||
return h.DoActionFilter(widgets.RecentComments, str.Replace(s, args))
|
||||
}
|
||||
|
@ -2,13 +2,13 @@ package widget
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/app/cmd/reload"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/cache"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/constraints"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/constraints/widgets"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
||||
"github.com/fthvgb1/wp-go/app/theme/wp"
|
||||
"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/slice"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
@ -25,7 +25,7 @@ var recentPostsTemplate = `{$before_widget}
|
||||
{$after_widget}
|
||||
`
|
||||
|
||||
func recentPostsArgs() map[string]string {
|
||||
func DefaultRecentPostsArgs() map[string]string {
|
||||
return map[string]string{
|
||||
"{$before_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{
|
||||
"number": int64(5),
|
||||
"show_date": false,
|
||||
@ -43,18 +43,24 @@ func recentConf() map[any]any {
|
||||
}
|
||||
}
|
||||
|
||||
func RecentPosts(h *wp.Handle, id string) string {
|
||||
conf := reload.GetAnyValBys("widget-recent-posts-conf", h, func(h *wp.Handle) map[any]any {
|
||||
recent := recentConf()
|
||||
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
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
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}"])
|
||||
@ -63,8 +69,11 @@ func RecentPosts(h *wp.Handle, id string) string {
|
||||
args["{$navCloser}"] = "</nav>"
|
||||
}
|
||||
return args
|
||||
})
|
||||
}
|
||||
|
||||
func RecentPosts(h *wp.Handle, id string) string {
|
||||
conf := GetRecentPostConf()
|
||||
args := GetRecentPostArgs(h, conf, id)
|
||||
currentPostId := uint64(0)
|
||||
if h.Scene() == constraints.Detail {
|
||||
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)
|
||||
})
|
||||
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))
|
||||
}
|
||||
|
@ -2,11 +2,11 @@ package widget
|
||||
|
||||
import (
|
||||
"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/widgets"
|
||||
"github.com/fthvgb1/wp-go/app/theme/wp"
|
||||
"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/maps"
|
||||
"github.com/fthvgb1/wp-go/helper/slice"
|
||||
@ -47,11 +47,14 @@ func searchArgs() map[string]string {
|
||||
|
||||
var form = html5SearchForm
|
||||
|
||||
func Search(h *wp.Handle, id string) string {
|
||||
args := reload.GetAnyValBys("widget-search-args", h, func(h *wp.Handle) map[string]string {
|
||||
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(h, widgets.Widget, map[string]string{})
|
||||
args := wp.GetComponentsArgs(h, widgets.Search, search)
|
||||
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}"] == "" {
|
||||
@ -70,12 +73,15 @@ func Search(h *wp.Handle, id string) string {
|
||||
}
|
||||
|
||||
return args
|
||||
})
|
||||
}
|
||||
|
||||
func Search(h *wp.Handle, id string) string {
|
||||
args := GetSearchArgs(h, id)
|
||||
s := strings.ReplaceAll(searchTemplate, "{$form}", form)
|
||||
val := ""
|
||||
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)
|
||||
return h.ComponentFilterFnHook(widgets.Search, str.Replace(s, args))
|
||||
return h.DoActionFilter(widgets.Search, str.Replace(s, args))
|
||||
}
|
||||
|
@ -3,32 +3,38 @@ package wp
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/app/cmd/reload"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/cache"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/logs"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
||||
"github.com/fthvgb1/wp-go/cache/reload"
|
||||
"github.com/fthvgb1/wp-go/helper/slice"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"github.com/fthvgb1/wp-go/model"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (h *Handle) DisplayHeaderText() bool {
|
||||
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) {
|
||||
var err error
|
||||
img := reload.GetAnyValBys("headerImages", h.theme, func(theme string) []models.PostThumbnail {
|
||||
hs, er := h.GetHeaderImages(h.theme)
|
||||
if er != nil {
|
||||
err = er
|
||||
return nil
|
||||
}
|
||||
return hs
|
||||
})
|
||||
if err != nil {
|
||||
img := GetCustomHeaderImgFn(h)
|
||||
err = h.Err()
|
||||
if err != nil && strings.Contains(err.Error(), "get customheadimage err") {
|
||||
logs.Error(err, "获取页眉背景图失败")
|
||||
return
|
||||
}
|
||||
@ -85,7 +91,7 @@ func GetVideoSetting(h *Handle, u string) (string, error) {
|
||||
if is := videoReg.FindString(u); is != "" {
|
||||
v.MimeType = "video/x-youtube"
|
||||
}
|
||||
_ = h.ComponentFilterFnHook("videoSetting", "", &v)
|
||||
_ = h.DoActionFilter("videoSetting", "", &v)
|
||||
s, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@ -134,13 +140,14 @@ func CustomVideo(h *Handle, scene ...string) (ok bool) {
|
||||
}
|
||||
scripts = slice.Map(scripts, func(t string) string {
|
||||
return fmt.Sprintf(`<script src="%s" id="wp-%s-js"></script>
|
||||
`, t, str.Replaces(t, [][]string{
|
||||
{"/wp-includes/js/dist/vendor/"},
|
||||
{"/wp-includes/js/dist/"},
|
||||
{"/wp-includes/js/"},
|
||||
{".min.js"},
|
||||
{".js"},
|
||||
{"wp-", ""},
|
||||
`, t, str.Replaces(t, []string{
|
||||
"/wp-includes/js/dist/vendor/",
|
||||
"/wp-includes/js/dist/",
|
||||
"/wp-includes/js/",
|
||||
".min.js",
|
||||
".js",
|
||||
"wp-",
|
||||
"",
|
||||
}))
|
||||
})
|
||||
|
||||
|
@ -2,9 +2,9 @@ package wp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/app/cmd/reload"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/cache"
|
||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
||||
"github.com/fthvgb1/wp-go/cache/reload"
|
||||
"github.com/fthvgb1/wp-go/helper/maps"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
)
|
||||
@ -42,10 +42,10 @@ func CalCustomLogo(h *Handle) (r string) {
|
||||
return
|
||||
}
|
||||
|
||||
var GetCustomLog = reload.BuildValFn("customLogo", CalCustomLogo)
|
||||
|
||||
func customLogo(h *Handle) func() string {
|
||||
return func() string {
|
||||
return reload.GetAnyValBys("customLogo", h, func(h *Handle) string {
|
||||
return CalCustomLogo(h)
|
||||
})
|
||||
return GetCustomLog(h)
|
||||
}
|
||||
}
|
||||
|
@ -10,18 +10,34 @@ import (
|
||||
"github.com/fthvgb1/wp-go/app/plugins"
|
||||
"github.com/fthvgb1/wp-go/app/plugins/wpposts"
|
||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
||||
"github.com/fthvgb1/wp-go/cache/cachemanager"
|
||||
"github.com/fthvgb1/wp-go/helper/number"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"github.com/fthvgb1/wp-go/plugin/pagination"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type DetailHandle struct {
|
||||
*Handle
|
||||
CommentRender plugins.CommentHtml
|
||||
Comments []models.Comments
|
||||
Comments []uint64
|
||||
Page int
|
||||
Limit int
|
||||
Post models.Posts
|
||||
CommentPageEle pagination.Render
|
||||
TotalRaw int
|
||||
TotalPage int
|
||||
CommentStep int
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -30,7 +46,7 @@ func (d *DetailHandle) BuildDetailData() (err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
d.Comment()
|
||||
d.CommentData()
|
||||
d.ContextPost()
|
||||
return
|
||||
}
|
||||
@ -68,12 +84,66 @@ func (d *DetailHandle) PasswordProject() {
|
||||
}
|
||||
}
|
||||
}
|
||||
func (d *DetailHandle) Comment() {
|
||||
comments, err := cache.PostComments(d.C, d.Post.Id)
|
||||
logs.IfError(err, "get d.Post comment", d.Post.Id)
|
||||
d.ginH["comments"] = comments
|
||||
d.Comments = comments
|
||||
|
||||
func (h *Handle) GetDetailHandle() *DetailHandle {
|
||||
v, ok := h.C.Get("detailHandle")
|
||||
if !ok {
|
||||
vv := NewDetailHandle(h)
|
||||
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() {
|
||||
@ -86,9 +156,21 @@ func (d *DetailHandle) RenderComment() {
|
||||
ableComment = false
|
||||
}
|
||||
d.ginH["showComment"] = ableComment
|
||||
if len(d.Comments) > 0 && ableComment {
|
||||
dep := str.ToInteger(wpconfig.GetOption("thread_comments_depth"), 5)
|
||||
d.ginH["comments"] = plugins.FormatComments(d.C, d.CommentRender, d.Comments, dep)
|
||||
d.ginH["comments"] = ""
|
||||
if len(d.Comments) < 0 || !ableComment {
|
||||
return
|
||||
}
|
||||
var err error
|
||||
d.ginH["comments"], err = RenderComment(d.C, d.Page, d.CommentRender, d.Comments, 2*time.Second, d.IsHttps())
|
||||
if err != nil {
|
||||
d.SetErr(err, 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 {
|
||||
return
|
||||
}
|
||||
d := h.Detail
|
||||
d := h.GetDetailHandle()
|
||||
d.PasswordProject()
|
||||
d.RenderComment()
|
||||
d.ginH["post"] = d.Post
|
||||
}
|
||||
|
||||
func Detail(h *Handle) {
|
||||
err := h.Detail.BuildDetailData()
|
||||
d := h.GetDetailHandle()
|
||||
err := d.BuildDetailData()
|
||||
if err != nil {
|
||||
h.Detail.SetErr(err)
|
||||
d.SetErr(err, High)
|
||||
}
|
||||
h.SetData("scene", h.Scene())
|
||||
}
|
||||
@ -120,7 +203,7 @@ func Detail(h *Handle) {
|
||||
func ReplyCommentJs(h *Handle) {
|
||||
h.PushFooterScript(constraints.Detail, NewComponent("comment-reply.js", "", false, 10, func(h *Handle) string {
|
||||
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>`
|
||||
}
|
||||
return reply
|
||||
|
8
app/theme/wp/errorlevel.go
Normal file
8
app/theme/wp/errorlevel.go
Normal file
@ -0,0 +1,8 @@
|
||||
package wp
|
||||
|
||||
const (
|
||||
None = iota
|
||||
Low
|
||||
High
|
||||
Fatal
|
||||
)
|
@ -2,13 +2,14 @@ package wp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/fthvgb1/wp-go/safety"
|
||||
)
|
||||
|
||||
var fnMap map[string]map[string]any
|
||||
var fnHook map[string]map[string]any
|
||||
var fnMap = safety.NewMap[string, map[string]any]()
|
||||
var fnHook = safety.NewMap[string, map[string]any]()
|
||||
|
||||
func GetFn[T any](fnType string, name string) []T {
|
||||
v, ok := fnMap[fnType]
|
||||
v, ok := fnMap.Load(fnType)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
@ -19,7 +20,7 @@ func GetFn[T any](fnType string, name string) []T {
|
||||
return vv.([]T)
|
||||
}
|
||||
func GetFnHook[T any](fnType string, name string) []T {
|
||||
v, ok := fnHook[fnType]
|
||||
v, ok := fnHook.Load(fnType)
|
||||
if !ok {
|
||||
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 {
|
||||
v, ok := fnMap[fnType]
|
||||
v, ok := fnMap.Load(fnType)
|
||||
if !ok {
|
||||
v = make(map[string]any)
|
||||
fnMap[fnType] = v
|
||||
fnMap.Store(fnType, v)
|
||||
v[name] = fns
|
||||
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 {
|
||||
v, ok := fnHook[fnType]
|
||||
v, ok := fnHook.Load(fnType)
|
||||
if !ok {
|
||||
v = make(map[string]any)
|
||||
fnHook[fnType] = v
|
||||
fnHook.Store(fnType, v)
|
||||
v[name] = fns
|
||||
return nil
|
||||
}
|
||||
|
@ -2,12 +2,13 @@ package wp
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/app/cmd/reload"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/cache"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/constraints"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
||||
"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/slice"
|
||||
"github.com/fthvgb1/wp-go/model"
|
||||
@ -19,11 +20,21 @@ type IndexHandle struct {
|
||||
*Handle
|
||||
Param *IndexParams
|
||||
Posts []models.Posts
|
||||
pageEle pagination.Elements
|
||||
pageEle pagination.Render
|
||||
TotalRows int
|
||||
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) {
|
||||
return i.postsPlugin
|
||||
}
|
||||
@ -32,11 +43,11 @@ func (i *IndexHandle) SetListPlugin(listPlugin func(*Handle, *models.Posts)) {
|
||||
i.postsPlugin = listPlugin
|
||||
}
|
||||
|
||||
func (i *IndexHandle) PageEle() pagination.Elements {
|
||||
func (i *IndexHandle) PageEle() pagination.Render {
|
||||
return i.pageEle
|
||||
}
|
||||
|
||||
func (i *IndexHandle) SetPageEle(pageEle pagination.Elements) {
|
||||
func (i *IndexHandle) SetPageEle(pageEle pagination.Render) {
|
||||
i.pageEle = pageEle
|
||||
}
|
||||
|
||||
@ -88,14 +99,14 @@ func (i *IndexHandle) GetIndexData() (posts []models.Posts, totalRaw int, err er
|
||||
switch i.scene {
|
||||
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 {
|
||||
i.MarkSticky(&posts)
|
||||
}
|
||||
|
||||
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:
|
||||
i.ginH["archiveYear"] = i.Param.Year
|
||||
@ -115,34 +126,36 @@ func (i *IndexHandle) Pagination() {
|
||||
if 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, paginations)
|
||||
i.ginH["pagination"] = pagination.Paginate(i.pageEle, i.TotalRows, i.Param.PageSize, i.Param.Page, i.Param.PaginationStep, *i.C.Request.URL, i.IsHttps())
|
||||
|
||||
}
|
||||
|
||||
func (i *IndexHandle) BuildIndexData(parm *IndexParams) (err error) {
|
||||
err = i.ParseIndex(parm)
|
||||
func (i *IndexHandle) BuildIndexData() (err error) {
|
||||
if i.Param == nil {
|
||||
i.Param = NewIndexParams(i.C)
|
||||
}
|
||||
err = i.ParseIndex(i.Param)
|
||||
if err != nil {
|
||||
i.Stats = constraints.ParamError
|
||||
return
|
||||
}
|
||||
posts, totalRows, err := i.GetIndexData()
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
i.Stats = constraints.Error404
|
||||
return
|
||||
}
|
||||
i.Posts = posts
|
||||
i.TotalRows = totalRows
|
||||
i.ginH["totalPage"] = number.CalTotalPage(totalRows, i.Param.PageSize)
|
||||
i.ginH["totalPage"] = number.DivideCeil(totalRows, i.Param.PageSize)
|
||||
return
|
||||
}
|
||||
|
||||
var GetPostsPlugin = reload.BuildValFnWithAnyParams("postPlugins", UsePostsPlugins)
|
||||
|
||||
func (i *IndexHandle) ExecPostsPlugin() {
|
||||
fn := i.postsPlugin
|
||||
if fn == nil {
|
||||
fn = reload.GetAnyValBys("postPlugins", i, func(a *IndexHandle) PostsPlugin {
|
||||
return UsePostsPlugins()
|
||||
})
|
||||
fn = GetPostsPlugin()
|
||||
}
|
||||
for j := range i.Posts {
|
||||
fn(i.Handle, &i.Posts[j])
|
||||
@ -150,27 +163,27 @@ func (i *IndexHandle) ExecPostsPlugin() {
|
||||
}
|
||||
|
||||
func IndexRender(h *Handle) {
|
||||
i := h.Index
|
||||
i := h.GetIndexHandle()
|
||||
i.ExecPostsPlugin()
|
||||
i.Pagination()
|
||||
i.ginH["posts"] = i.Posts
|
||||
}
|
||||
|
||||
func Index(h *Handle) {
|
||||
i := h.Index
|
||||
err := i.BuildIndexData(NewIndexParams(i.C))
|
||||
i := h.GetIndexHandle()
|
||||
err := i.BuildIndexData()
|
||||
if err != nil {
|
||||
i.SetErr(err)
|
||||
i.SetErr(err, High)
|
||||
}
|
||||
h.SetData("scene", h.Scene())
|
||||
}
|
||||
|
||||
func (i *IndexHandle) MarkSticky(posts *[]models.Posts) {
|
||||
a := i.StickPosts()
|
||||
a := GetStickPosts(i.Handle)
|
||||
if len(a) < 1 {
|
||||
return
|
||||
}
|
||||
m := i.StickMapPosts()
|
||||
m := GetStickMapPosts(i.Handle)
|
||||
*posts = append(a, slice.Filter(*posts, func(post models.Posts, _ int) bool {
|
||||
_, ok := m[post.Id]
|
||||
return !ok
|
||||
|
@ -105,7 +105,6 @@ func NewIndexParams(ctx *gin.Context) *IndexParams {
|
||||
|
||||
func (i *IndexParams) ParseSearchs() {
|
||||
s := i.Ctx.Query("s")
|
||||
if s != "" {
|
||||
q := str.Join("%", s, "%")
|
||||
i.Where = append(i.Where, []string{
|
||||
"and", "post_title", "like", q, "",
|
||||
@ -116,7 +115,6 @@ func (i *IndexParams) ParseSearchs() {
|
||||
i.Header = fmt.Sprintf("<span>%s</span>的搜索结果", s)
|
||||
i.setTitleLR(str.Join(`"`, s, `"`, "的搜索结果"), i.BlogName)
|
||||
i.Search = s
|
||||
}
|
||||
}
|
||||
func (i *IndexParams) ParseArchives() error {
|
||||
year := i.Ctx.Param("year")
|
||||
|
@ -1,11 +1,11 @@
|
||||
package wp
|
||||
|
||||
import (
|
||||
"github.com/fthvgb1/wp-go/app/cmd/reload"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/config"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
||||
"github.com/fthvgb1/wp-go/app/plugins"
|
||||
"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/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){
|
||||
"passwordProject": PasswordProject,
|
||||
"digest": Digest,
|
||||
})
|
||||
}, "list-post-plugins-fns")
|
||||
|
||||
func (h *Handle) PushPostsPlugin(name string, fn func(PostsPlugin, *Handle, *models.Posts)) {
|
||||
m := pluginFns.Load()
|
||||
@ -53,7 +53,7 @@ func Digest(next PostsPlugin, h *Handle, post *models.Posts) {
|
||||
next(h, post)
|
||||
}
|
||||
|
||||
var ordinaryPlugin = reload.Vars([]PostsPlugin{})
|
||||
var ordinaryPlugin = reload.Vars([]PostsPlugin{}, "ordinaryPlugin")
|
||||
|
||||
func (h *Handle) PushPostPlugin(plugin ...PostsPlugin) {
|
||||
p := ordinaryPlugin.Load()
|
||||
@ -69,7 +69,7 @@ func PostPlugin(calls ...PostsPlugin) PostsPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
func UsePostsPlugins() PostsPlugin {
|
||||
func UsePostsPlugins(_ ...any) PostsPlugin {
|
||||
m := pluginFns.Load()
|
||||
pluginss := slice.FilterAndMap(config.GetConfig().ListPagePlugins, func(t string) (func(PostsPlugin, *Handle, *models.Posts), bool) {
|
||||
f, ok := m[t]
|
||||
|
167
app/theme/wp/middleware/middleware.go
Normal file
167
app/theme/wp/middleware/middleware.go
Normal 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"),
|
||||
)
|
||||
}
|
@ -1,9 +1,8 @@
|
||||
package wp
|
||||
|
||||
import (
|
||||
"github.com/fthvgb1/wp-go/app/cmd/reload"
|
||||
"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"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
)
|
||||
@ -22,7 +21,7 @@ func NewPipe(name string, order float64, fn HandlePipeFn[*Handle]) Pipe {
|
||||
|
||||
// HandlePipe 方便把功能写在其它包里
|
||||
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) {
|
||||
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) {
|
||||
if _, ok := h.handlers[pipScene]; !ok {
|
||||
h.handlers[pipScene] = make(map[string][]HandleCall)
|
||||
v, ok := handlerss.Load(pipScene)
|
||||
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) {
|
||||
@ -65,27 +66,16 @@ func (h *Handle) PushDataHandler(scene string, fns ...HandleCall) {
|
||||
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) {
|
||||
key := keyFn(h, pipeScene)
|
||||
handlers := reload.GetAnyValMapBy("pipeHandlers", key, h, func(h *Handle) []HandleCall {
|
||||
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
|
||||
})
|
||||
handlers := pipeHandlerFn(key, h)
|
||||
for _, handler := range handlers {
|
||||
handler.Fn(h)
|
||||
if h.abort {
|
||||
@ -98,22 +88,63 @@ func BuildPipe(pipeScene string, keyFn func(*Handle, string) string, fn func(*Ha
|
||||
}
|
||||
}
|
||||
|
||||
func PipeKey(h *Handle, pipScene string) string {
|
||||
key := str.Join("pipekey", "-", pipScene, "-", h.scene, "-", h.Stats)
|
||||
return h.ComponentFilterFnHook("pipeKey", key, pipScene)
|
||||
func BuildHandler(pipeScene string, keyFn func(*Handle, string) string,
|
||||
handleHook func(*Handle, string,
|
||||
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)) {
|
||||
if !helper.GetContextVal(h.C, "inited", false) {
|
||||
if !h.isInited {
|
||||
InitHandle(conf, h)
|
||||
}
|
||||
reload.GetAnyValBys(str.Join("pipeInit-", h.scene), h, func(h *Handle) func(*Handle) {
|
||||
p := GetFn[Pipe]("pipe", constraints.AllScene)
|
||||
p = append(p, GetFn[Pipe]("pipe", h.scene)...)
|
||||
pipes := slice.FilterAndMap(p, func(pipe Pipe) (Pipe, bool) {
|
||||
pipeInitFn(h.scene, h.scene)(h)
|
||||
}
|
||||
|
||||
func BuildPipe(scene string) func(*Handle) {
|
||||
pipees := GetFn[Pipe]("pipe", constraints.AllScene)
|
||||
pipees = append(pipees, GetFn[Pipe]("pipe", scene)...)
|
||||
pipes := slice.FilterAndMap(pipees, func(pipe Pipe) (Pipe, bool) {
|
||||
var ok bool
|
||||
mut := reload.GetGlobeMutex()
|
||||
mut.Lock()
|
||||
hooks := GetFnHook[func(Pipe) (Pipe, bool)]("pipeHook", constraints.AllScene)
|
||||
hooks = append(hooks, GetFnHook[func(Pipe) (Pipe, bool)]("pipeHook", h.scene)...)
|
||||
hooks = append(hooks, GetFnHook[func(Pipe) (Pipe, bool)]("pipeHook", scene)...)
|
||||
mut.Unlock()
|
||||
for _, fn := range hooks {
|
||||
pipe, ok = fn(pipe)
|
||||
if !ok {
|
||||
@ -122,63 +153,76 @@ func Run(h *Handle, conf func(*Handle)) {
|
||||
}
|
||||
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
|
||||
})
|
||||
return HandlePipe(NothingToDo, arr...)
|
||||
})(h)
|
||||
}
|
||||
|
||||
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) {
|
||||
handlers = append(handlers, middlewares[h.scene]...)
|
||||
handlers = append(handlers, middlewares[constraints.AllScene]...)
|
||||
handlers = h.PipeHandleHook("PipeMiddlewareHandle", handlers, middlewares, key)
|
||||
return
|
||||
func PipeMiddlewareHandle(h *Handle, key string,
|
||||
hooks map[string][]func(HandleCall) (HandleCall, bool),
|
||||
handlers map[string][]HandleCall) []HandleCall {
|
||||
hookedHandles := HookHandles(hooks, handlers, h.scene, constraints.AllScene)
|
||||
return h.PipeHandleHook("PipeMiddlewareHandle", hookedHandles, handlers, key)
|
||||
}
|
||||
|
||||
func PipeDataHandle(h *Handle, dataHandlers map[string][]HandleCall, key string) (handlers []HandleCall) {
|
||||
handlers = append(handlers, dataHandlers[h.scene]...)
|
||||
handlers = append(handlers, dataHandlers[constraints.AllScene]...)
|
||||
handlers = h.PipeHandleHook("PipeDataHandle", handlers, dataHandlers, key)
|
||||
return
|
||||
func PipeDataHandle(h *Handle, key string,
|
||||
hooks map[string][]func(HandleCall) (HandleCall, bool),
|
||||
handlers map[string][]HandleCall) []HandleCall {
|
||||
hookedHandles := HookHandles(hooks, handlers, h.scene, constraints.AllScene)
|
||||
return h.PipeHandleHook("PipeDataHandle", hookedHandles, handlers, key)
|
||||
}
|
||||
|
||||
func PipeRender(h *Handle, renders map[string][]HandleCall, key string) (handlers []HandleCall) {
|
||||
handlers = append(handlers, renders[h.Stats]...)
|
||||
handlers = append(handlers, renders[h.scene]...)
|
||||
handlers = append(handlers, renders[constraints.AllStats]...)
|
||||
handlers = append(handlers, renders[constraints.AllScene]...)
|
||||
handlers = h.PipeHandleHook("PipeRender", handlers, renders, key)
|
||||
return
|
||||
func PipeRender(h *Handle, key string,
|
||||
hooks map[string][]func(HandleCall) (HandleCall, bool),
|
||||
handlers map[string][]HandleCall) []HandleCall {
|
||||
hookedHandles := HookHandles(hooks, handlers, h.scene, h.Stats, constraints.AllScene, constraints.AllStats)
|
||||
return h.PipeHandleHook("PipeRender", hookedHandles, handlers, key)
|
||||
}
|
||||
|
||||
// DeleteHandle 写插件的时候用
|
||||
func (h *Handle) DeleteHandle(pipeScene string, name string) {
|
||||
h.handleHook[pipeScene] = append(h.handleHook[pipeScene], func(call HandleCall) (HandleCall, bool) {
|
||||
func (h *Handle) DeleteHandle(pipeScene, scene, name string) {
|
||||
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
|
||||
})
|
||||
handleHooks.Store(pipeScene, v)
|
||||
}
|
||||
|
||||
// ReplaceHandle 写插件的时候用
|
||||
func (h *Handle) ReplaceHandle(pipeScene, name string, fn HandleFn[*Handle]) {
|
||||
h.handleHook[pipeScene] = append(h.handleHook[pipeScene], func(call HandleCall) (HandleCall, bool) {
|
||||
func (h *Handle) ReplaceHandle(pipeScene, scene, name string, fn HandleFn[*Handle]) {
|
||||
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 {
|
||||
call.Fn = fn
|
||||
}
|
||||
return call, true
|
||||
})
|
||||
handleHooks.Store(pipeScene, v)
|
||||
}
|
||||
|
||||
// HookHandle 写插件的时候用
|
||||
func (h *Handle) HookHandle(pipeScene string, hook func(HandleCall) (HandleCall, bool)) {
|
||||
h.handleHook[pipeScene] = append(h.handleHook[pipeScene], hook)
|
||||
func (h *Handle) HookHandle(pipeScene, scene string, hook func(HandleCall) (HandleCall, bool)) {
|
||||
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 {
|
||||
@ -193,11 +237,11 @@ func (h *Handle) PipeHandleHook(name string, calls []HandleCall, m map[string][]
|
||||
}
|
||||
|
||||
func InitPipe(h *Handle) {
|
||||
h.PushPipe(constraints.Home, NewPipe(constraints.PipeMiddleware, 300,
|
||||
BuildPipe(constraints.PipeMiddleware, MiddlewareKey, PipeMiddlewareHandle)))
|
||||
h.PushPipe(constraints.AllScene, NewPipe(constraints.PipeMiddleware, 300,
|
||||
BuildHandlers(constraints.PipeMiddleware, MiddlewareKey, PipeMiddlewareHandle)))
|
||||
|
||||
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,
|
||||
BuildPipe(constraints.PipeRender, PipeKey, PipeRender)))
|
||||
BuildHandlers(constraints.PipeRender, PipeKey, PipeRender)))
|
||||
}
|
||||
|
@ -1,13 +1,16 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"github.com/fthvgb1/wp-go/app/cmd/reload"
|
||||
"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/safety"
|
||||
"net/http"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// Route
|
||||
// Type value equal const or reg
|
||||
type Route struct {
|
||||
Path string
|
||||
Scene string
|
||||
@ -20,18 +23,18 @@ var routeHook []func(Route) (Route, bool)
|
||||
var regRoutes *safety.Map[string, *regexp.Regexp]
|
||||
var routes = func() *safety.Map[string, Route] {
|
||||
r := safety.NewMap[string, Route]()
|
||||
reload.Push(func() {
|
||||
reload.Append(func() {
|
||||
r.Flush()
|
||||
regRoutes.Flush()
|
||||
})
|
||||
}, "wp-routers")
|
||||
regRoutes = safety.NewMap[string, *regexp.Regexp]()
|
||||
return r
|
||||
}()
|
||||
|
||||
// PushRoute path can be const or regex string
|
||||
//
|
||||
// eg: `(?P<control>\w+)/(?P<method>\w+)`, route.Route{
|
||||
// Path: `(?P<control>\w+)/(?P<method>\w+)`,
|
||||
// eg: `(?P<controller>\w+)/(?P<method>\w+)`, route.Route{
|
||||
// Path: `(?P<controller>\w+)/(?P<method>\w+)`,
|
||||
// Scene: constraints.Home,
|
||||
// Method: []string{"GET"},
|
||||
// Type: "reg",
|
||||
@ -70,11 +73,10 @@ func Hook(path string, fn func(Route) Route) {
|
||||
return r, path == r.Path
|
||||
})
|
||||
}
|
||||
func ResolveRoute(h *wp.Handle) {
|
||||
requestURI := h.C.Request.RequestURI
|
||||
rs, rrs := reload.GetAnyValBys("route",
|
||||
struct{}{},
|
||||
func(_ struct{}) func() (map[string]Route, map[string]*regexp.Regexp) {
|
||||
|
||||
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 {
|
||||
@ -82,9 +84,13 @@ func ResolveRoute(h *wp.Handle) {
|
||||
if len(routeHook) > 0 {
|
||||
for _, fn := range routeHook {
|
||||
v, ok := fn(value)
|
||||
if ok {
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
m[v.Path] = v
|
||||
if v.Type == "reg" {
|
||||
if v.Type != "reg" {
|
||||
continue
|
||||
}
|
||||
if v.Path != key {
|
||||
vvv, err := regexp.Compile(v.Path)
|
||||
if err != nil {
|
||||
@ -94,36 +100,47 @@ func ResolveRoute(h *wp.Handle) {
|
||||
}
|
||||
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) {
|
||||
requestURI := h.C.Request.RequestURI
|
||||
rs, rrs := RegRouteFn()()
|
||||
v, ok := rs[requestURI]
|
||||
if ok && slice.IsContained(v.Method, h.C.Request.Method) {
|
||||
h.SetScene(v.Scene)
|
||||
wp.Run(h, nil)
|
||||
h.Abort()
|
||||
return
|
||||
}
|
||||
for path, reg := range rrs {
|
||||
r := reg.FindAllStringSubmatch(requestURI, -1)
|
||||
r := reg.FindStringSubmatch(requestURI)
|
||||
if len(r) < 1 {
|
||||
return
|
||||
}
|
||||
rr := rs[path]
|
||||
if slice.IsContained(rr.Method, h.C.Request.Method) {
|
||||
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)
|
||||
h.Abort()
|
||||
return
|
||||
}
|
||||
}
|
||||
h.C.Status(http.StatusNotFound)
|
||||
h.Abort()
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ import (
|
||||
"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) {
|
||||
id := str.ToInteger[uint64](wpconfig.GetOption("site_icon"), 0)
|
||||
@ -21,7 +21,7 @@ func CalSiteIcon(h *Handle) (r string) {
|
||||
return
|
||||
}
|
||||
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]
|
||||
if !ok {
|
||||
return "", false
|
||||
|
@ -3,18 +3,18 @@ package wp
|
||||
import (
|
||||
"fmt"
|
||||
"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/logs"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/models"
|
||||
"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"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
)
|
||||
|
||||
func (h *Handle) StickPosts() []models.Posts {
|
||||
return reload.GetAnyValBys("stickPostsSlice", h, func(h *Handle) (r []models.Posts) {
|
||||
var GetStickPosts = reload.BuildValFnWithConfirm("stickPostsSlice", ParseStickPosts)
|
||||
|
||||
func ParseStickPosts(h *Handle) (r []models.Posts, ok bool) {
|
||||
v := wpconfig.GetOption("sticky_posts")
|
||||
if v == "" {
|
||||
return
|
||||
@ -30,18 +30,19 @@ func (h *Handle) StickPosts() []models.Posts {
|
||||
post.IsSticky = true
|
||||
return post, err == nil
|
||||
})
|
||||
ok = true
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
func (h *Handle) StickMapPosts() map[uint64]models.Posts {
|
||||
return reload.GetAnyValBys("stickPostsMap", h, func(h *Handle) map[uint64]models.Posts {
|
||||
return slice.SimpleToMap(h.StickPosts(), func(v models.Posts) uint64 {
|
||||
var GetStickMapPosts = reload.BuildValFn("stickPostsMap", StickMapPosts)
|
||||
|
||||
func StickMapPosts(h *Handle) map[uint64]models.Posts {
|
||||
return slice.SimpleToMap(GetStickPosts(h), func(v models.Posts) uint64 {
|
||||
return v.Id
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func (h *Handle) IsStick(id uint64) bool {
|
||||
return maps.IsExists(h.StickMapPosts(), id)
|
||||
_, ok := GetStickMapPosts(h)[id]
|
||||
return ok
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
{{define "common/head"}}
|
||||
|
||||
{{if .headScript}}
|
||||
{{.headScript|unescaped}}
|
||||
{{end}}
|
||||
{{ callFuncString .calComponent "headScript"}}
|
||||
|
||||
{{if .externHead}}
|
||||
{{.externHead|unescaped}}
|
||||
@ -10,9 +8,7 @@
|
||||
{{end}}
|
||||
|
||||
{{define "common/footer"}}
|
||||
{{if .footerScript}}
|
||||
{{.footerScript|unescaped}}
|
||||
{{end}}
|
||||
{{ callFuncString .calComponent "footerScript"}}
|
||||
{{if .externFooter}}
|
||||
{{.externFooter|unescaped}}
|
||||
{{end}}
|
||||
@ -20,7 +16,17 @@
|
||||
|
||||
|
||||
{{define "common/sidebarWidget"}}
|
||||
{{if .sidebarsWidgets}}
|
||||
{{.sidebarsWidgets|unescaped}}
|
||||
{{ callFuncString .calComponent "sidebarsWidgets"}}
|
||||
{{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}}
|
@ -1,26 +1,28 @@
|
||||
package wp
|
||||
|
||||
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/constraints"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/logs"
|
||||
"github.com/fthvgb1/wp-go/app/plugins/wphandle/apply"
|
||||
"github.com/fthvgb1/wp-go/app/wpconfig"
|
||||
"github.com/fthvgb1/wp-go/cache/reload"
|
||||
"github.com/fthvgb1/wp-go/helper/maps"
|
||||
str "github.com/fthvgb1/wp-go/helper/strings"
|
||||
"github.com/fthvgb1/wp-go/safety"
|
||||
"github.com/gin-contrib/sessions"
|
||||
"github.com/gin-gonic/gin"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Handle struct {
|
||||
Index *IndexHandle
|
||||
Detail *DetailHandle
|
||||
C *gin.Context
|
||||
theme string
|
||||
isInited bool
|
||||
Session sessions.Session
|
||||
ginH gin.H
|
||||
password string
|
||||
@ -28,17 +30,18 @@ type Handle struct {
|
||||
Code int
|
||||
Stats string
|
||||
templ string
|
||||
components map[string]map[string][]Components[string]
|
||||
componentHook map[string][]func(Components[string]) (Components[string], bool)
|
||||
themeMods wpconfig.ThemeMods
|
||||
handlers map[string]map[string][]HandleCall
|
||||
handleHook map[string][]func(HandleCall) (HandleCall, bool)
|
||||
err error
|
||||
errLevel int8
|
||||
abort bool
|
||||
stopPipe bool
|
||||
componentsArgs map[string]any
|
||||
componentFilterFn map[string][]func(*Handle, string, ...any) string
|
||||
template *template.Template
|
||||
}
|
||||
|
||||
var handlerss = safety.NewMap[string, map[string][]HandleCall]()
|
||||
var handleHooks = safety.NewMap[string, map[string][]func(HandleCall) (HandleCall, bool)]()
|
||||
|
||||
func (h *Handle) Theme() string {
|
||||
return h.theme
|
||||
}
|
||||
|
||||
func (h *Handle) GinH() gin.H {
|
||||
@ -49,28 +52,20 @@ func (h *Handle) SetScene(scene string) {
|
||||
h.scene = scene
|
||||
}
|
||||
|
||||
func (h *Handle) Components() map[string]map[string][]Components[string] {
|
||||
return h.components
|
||||
func (h *Handle) Components() *safety.Map[string, map[string][]Components[string]] {
|
||||
return handleComponents
|
||||
}
|
||||
|
||||
func (h *Handle) ComponentHook() map[string][]func(Components[string]) (Components[string], bool) {
|
||||
return h.componentHook
|
||||
func (h *Handle) ComponentHook() *safety.Map[string, map[string][]func(Components[string]) (Components[string], bool)] {
|
||||
return handleComponentHook
|
||||
}
|
||||
|
||||
func (h *Handle) Handlers() map[string]map[string][]HandleCall {
|
||||
return h.handlers
|
||||
func (h *Handle) Handlers() *safety.Map[string, map[string][]HandleCall] {
|
||||
return handlerss
|
||||
}
|
||||
|
||||
func (h *Handle) HandleHook() map[string][]func(HandleCall) (HandleCall, bool) {
|
||||
return h.handleHook
|
||||
}
|
||||
|
||||
func (h *Handle) SetTemplate(template *template.Template) {
|
||||
h.template = template
|
||||
}
|
||||
|
||||
func (h *Handle) Template() *template.Template {
|
||||
return h.template
|
||||
func (h *Handle) HandleHook() *safety.Map[string, map[string][]func(HandleCall) (HandleCall, bool)] {
|
||||
return handleHooks
|
||||
}
|
||||
|
||||
type HandlePlugins map[string]HandleFn[*Handle]
|
||||
@ -92,44 +87,50 @@ type HandleCall struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
func InitHandle(fn func(*Handle), h *Handle) {
|
||||
var inited = false
|
||||
hh := reload.GetAnyValBys("themeArgAndConfig", h, func(h *Handle) Handle {
|
||||
h.components = make(map[string]map[string][]Components[string])
|
||||
h.componentsArgs = make(map[string]any)
|
||||
h.componentFilterFn = make(map[string][]func(*Handle, string, ...any) string)
|
||||
h.handlers = make(map[string]map[string][]HandleCall)
|
||||
h.handleHook = make(map[string][]func(HandleCall) (HandleCall, bool))
|
||||
var isFirstRequest = true
|
||||
|
||||
func SetConfigHandle(a ...any) Handle {
|
||||
configFn := a[0].(func(*Handle))
|
||||
hh := a[1].(*Handle)
|
||||
h := &Handle{}
|
||||
handleComponents.Flush()
|
||||
componentsArgs.Flush()
|
||||
handleComponentHook.Flush()
|
||||
componentFilterFns.Flush()
|
||||
handlerss.Flush()
|
||||
handleHooks.Flush()
|
||||
h.ginH = gin.H{}
|
||||
fnMap = map[string]map[string]any{}
|
||||
fnHook = map[string]map[string]any{}
|
||||
fn(h)
|
||||
v := apply.UsePlugins()
|
||||
fnMap.Flush()
|
||||
fnHook.Flush()
|
||||
if isFirstRequest {
|
||||
isFirstRequest = false
|
||||
} else {
|
||||
reload.Reload()
|
||||
}
|
||||
h.C = hh.C
|
||||
h.theme = hh.theme
|
||||
configFn(h)
|
||||
v := apply.GetPlugins()
|
||||
pluginFn, ok := v.(func(*Handle))
|
||||
if ok {
|
||||
pluginFn(h)
|
||||
}
|
||||
h.C.Set("inited", true)
|
||||
inited = true
|
||||
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["calPostClass"] = postClass(h)
|
||||
h.ginH["calBodyClass"] = bodyClass(h)
|
||||
h.ginH["customLogo"] = customLogo(h)
|
||||
if inited {
|
||||
return
|
||||
}
|
||||
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)
|
||||
h.ginH["calComponent"] = CalComponent(h)
|
||||
h.isInited = true
|
||||
}
|
||||
|
||||
func (h *Handle) Abort() {
|
||||
@ -151,13 +152,25 @@ func (h *Handle) Err() error {
|
||||
return h.err
|
||||
}
|
||||
|
||||
func (h *Handle) SetErr(err error) {
|
||||
h.err = err
|
||||
func (h *Handle) SetErr(err error, level int8) {
|
||||
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) {
|
||||
h.templ = templ
|
||||
}
|
||||
func (h *Handle) GetTempl() string {
|
||||
return h.templ
|
||||
}
|
||||
|
||||
func (h *Handle) Scene() string {
|
||||
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 {
|
||||
mods, err := wpconfig.GetThemeMods(theme)
|
||||
logs.IfError(err, "获取mods失败")
|
||||
return &Handle{
|
||||
C: c,
|
||||
theme: theme,
|
||||
Session: sessions.Default(c),
|
||||
scene: scene,
|
||||
Stats: constraints.Ok,
|
||||
themeMods: mods,
|
||||
}
|
||||
}
|
||||
|
||||
@ -224,7 +234,12 @@ func (h *Handle) RenderHtml(t *template.Template, statsCode int, name string) {
|
||||
header["Content-Type"] = htmlContentType
|
||||
}
|
||||
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()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@ -239,8 +254,8 @@ func (h *Handle) PushHandlers(pipeScene string, call HandleCall, statsOrScene ..
|
||||
|
||||
func (h *Handle) CommonComponents() {
|
||||
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"))
|
||||
ReplyCommentJs(h)
|
||||
AdditionScript(h)
|
||||
}
|
||||
|
||||
@ -281,3 +296,10 @@ func NewHandleFn(fn HandleFn[*Handle], order float64, name string) HandleCall {
|
||||
func NothingToDo(h *Handle) {
|
||||
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"))
|
||||
}
|
||||
|
@ -39,7 +39,13 @@ func GetOption(k string) string {
|
||||
if ok {
|
||||
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)
|
||||
if err != nil {
|
||||
return ""
|
||||
|
@ -3,10 +3,10 @@ package wpconfig
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/app/cmd/reload"
|
||||
"github.com/fthvgb1/wp-go/app/phphelper"
|
||||
"github.com/fthvgb1/wp-go/app/pkg/logs"
|
||||
"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/safety"
|
||||
"path/filepath"
|
||||
@ -73,6 +73,7 @@ func Thumbnail(metadata models.WpAttachmentMetadata, Type, host string, except .
|
||||
up := strings.Split(metadata.File, "/")
|
||||
if metadata.File != "" && Type == "full" {
|
||||
mimeType := metadata.Sizes["thumbnail"].MimeType
|
||||
metadata.Sizes = maps.Copy(metadata.Sizes)
|
||||
metadata.Sizes["full"] = models.MetaDataFileSize{
|
||||
File: filepath.Base(metadata.File),
|
||||
Width: metadata.Width,
|
||||
@ -111,10 +112,10 @@ func Thumbnail(metadata models.WpAttachmentMetadata, Type, host string, except .
|
||||
var themeModes = func() *safety.Map[string, ThemeMods] {
|
||||
m := safety.NewMap[string, ThemeMods]()
|
||||
themeModsRaw = safety.NewMap[string, map[string]any]()
|
||||
reload.Push(func() {
|
||||
reload.Append(func() {
|
||||
m.Flush()
|
||||
themeModsRaw.Flush()
|
||||
})
|
||||
}, "theme-modes")
|
||||
|
||||
return m
|
||||
}()
|
||||
|
44
cache/cache.go
vendored
44
cache/cache.go
vendored
@ -2,15 +2,49 @@ package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Cache[K comparable, V any] interface {
|
||||
Get(ctx context.Context, key K) (V, bool)
|
||||
Set(ctx context.Context, key K, val V, expire time.Duration)
|
||||
Ttl(ctx context.Context, key K, expire time.Duration) time.Duration
|
||||
Ver(ctx context.Context, key K) int
|
||||
Set(ctx context.Context, key K, val V)
|
||||
GetExpireTime(ctx context.Context) time.Duration
|
||||
Ttl(ctx context.Context, key K) time.Duration
|
||||
Flush(ctx context.Context)
|
||||
Delete(ctx context.Context, key K)
|
||||
ClearExpired(ctx context.Context, expire time.Duration)
|
||||
Del(ctx context.Context, key ...K)
|
||||
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
163
cache/cachemanager/manger.go
vendored
Normal 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
193
cache/cachemanager/manger_test.go
vendored
Normal 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
148
cache/cachemanager/mapcache.go
vendored
Normal 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
57
cache/cachemanager/pagination.go
vendored
Normal 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
83
cache/cachemanager/varcache.go
vendored
Normal 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
78
cache/locks.go
vendored
Normal 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
|
||||
}
|
558
cache/map.go
vendored
558
cache/map.go
vendored
@ -4,54 +4,200 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"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"
|
||||
"time"
|
||||
)
|
||||
|
||||
type MapCache[K comparable, V any] struct {
|
||||
data Cache[K, V]
|
||||
mux sync.Mutex
|
||||
cacheFunc func(...any) (V, error)
|
||||
batchCacheFn func(...any) (map[K]V, error)
|
||||
expireTime time.Duration
|
||||
Cache[K, V]
|
||||
mux *sync.Mutex
|
||||
muFn func(ctx context.Context, gMut *sync.Mutex, k ...K) *sync.Mutex
|
||||
cacheFunc MapSingleFn[K, V]
|
||||
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
|
||||
}
|
||||
|
||||
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) {
|
||||
tt := m.data.Ttl(ctx, k, m.expireTime)
|
||||
tt := m.Ttl(ctx, k)
|
||||
if tt <= 0 {
|
||||
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
|
||||
if m.cacheFunc == nil {
|
||||
m.setCacheFn(fn)
|
||||
m.setDefaultCacheFn(fn)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MapCache[K, V]) setCacheFn(fn func(...any) (map[K]V, error)) {
|
||||
m.cacheFunc = func(a ...any) (V, error) {
|
||||
func (m *MapCache[K, V]) setDefaultCacheFn(fn MapBatchFn[K, V]) {
|
||||
m.cacheFunc = func(ctx context.Context, k K, a ...any) (V, error) {
|
||||
var err error
|
||||
var r map[K]V
|
||||
var k K
|
||||
ctx, ok := a[0].(context.Context)
|
||||
if ok {
|
||||
k, ok = a[1].(K)
|
||||
if ok {
|
||||
r, err = fn(ctx, []K{k})
|
||||
}
|
||||
}
|
||||
r, err = fn(ctx, []K{k}, a...)
|
||||
|
||||
if err != nil {
|
||||
var rr V
|
||||
@ -61,59 +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) {
|
||||
m.mux.Lock()
|
||||
defer m.mux.Unlock()
|
||||
m.data.Flush(ctx)
|
||||
m.flush(ctx)
|
||||
}
|
||||
|
||||
func (m *MapCache[K, V]) Get(ctx context.Context, k K) (V, bool) {
|
||||
return m.data.Get(ctx, k)
|
||||
}
|
||||
|
||||
func (m *MapCache[K, V]) Set(ctx context.Context, k K, v V) {
|
||||
m.data.Set(ctx, k, v, m.expireTime)
|
||||
func (m *MapCache[K, V]) increaseUpdates(c context.Context, timeout time.Duration, data V, key K, params ...any) (V, error) {
|
||||
var err error
|
||||
nowTime := time.Now()
|
||||
if nowTime.Sub(m.GetLastSetTime(c, key)) < m.increaseUpdate.CycleTime() {
|
||||
return data, err
|
||||
}
|
||||
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) {
|
||||
data, ok := m.data.Get(c, key)
|
||||
data, ok := m.Get(c, key)
|
||||
var err error
|
||||
if !ok || m.data.Ttl(c, key, m.expireTime) <= 0 {
|
||||
ver := m.data.Ver(c, key)
|
||||
if ok {
|
||||
if m.increaseUpdate == nil || m.refresh == nil {
|
||||
return data, err
|
||||
}
|
||||
return m.increaseUpdates(c, timeout, data, key, params...)
|
||||
}
|
||||
call := func() {
|
||||
m.mux.Lock()
|
||||
defer m.mux.Unlock()
|
||||
if m.data.Ver(c, key) > ver {
|
||||
data, _ = m.data.Get(c, key)
|
||||
l := m.muFn(c, m.mux, key)
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
if data, ok = m.Get(c, key); ok {
|
||||
return
|
||||
}
|
||||
data, err = m.cacheFunc(params...)
|
||||
data, err = m.cacheFunc(c, key, params...)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
m.Set(c, key, data)
|
||||
}
|
||||
if timeout > 0 {
|
||||
ctx, cancel := context.WithTimeout(c, timeout)
|
||||
er := helper.RunFnWithTimeout(c, timeout, call, fmt.Sprintf("get cache %v ", key))
|
||||
if err == nil && er != nil {
|
||||
err = er
|
||||
}
|
||||
} else {
|
||||
call()
|
||||
}
|
||||
return data, err
|
||||
}
|
||||
|
||||
func (m *MapCache[K, V]) GetCacheBatch(c context.Context, key []K, timeout time.Duration, params ...any) ([]V, error) {
|
||||
return m.getCacheBatch(c, key, timeout, params...)
|
||||
}
|
||||
|
||||
func (m *MapCache[K, V]) GetBatchToMap(c context.Context, key []K, timeout time.Duration, params ...any) (map[K]V, error) {
|
||||
return m.getCacheBatchToMap(c, key, timeout, params...)
|
||||
}
|
||||
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 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
|
||||
}
|
||||
}
|
||||
|
||||
call := func() {
|
||||
m.mux.Lock()
|
||||
defer m.mux.Unlock()
|
||||
mmm, er := e.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[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 {
|
||||
ctx, cancel := context.WithTimeout(ctx, timeout)
|
||||
defer cancel()
|
||||
done := make(chan struct{}, 1)
|
||||
go func() {
|
||||
@ -123,47 +355,59 @@ func (m *MapCache[K, V]) GetCache(c context.Context, key K, timeout time.Duratio
|
||||
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 data, err
|
||||
}
|
||||
|
||||
func (m *MapCache[K, V]) GetCacheBatch(c context.Context, key []K, timeout time.Duration, params ...any) ([]V, error) {
|
||||
var res []V
|
||||
ver := 0
|
||||
needFlush := slice.FilterAndMap(key, func(k K) (r K, ok bool) {
|
||||
if _, ok := m.data.Get(c, k); !ok {
|
||||
return k, true
|
||||
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
|
||||
}
|
||||
ver += m.data.Ver(c, k)
|
||||
}
|
||||
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
|
||||
})
|
||||
|
||||
var err error
|
||||
if len(needFlush) > 0 {
|
||||
call := func() {
|
||||
m.mux.Lock()
|
||||
defer m.mux.Unlock()
|
||||
|
||||
vers := slice.Reduce(needFlush, func(t K, r int) int {
|
||||
return r + m.data.Ver(c, t)
|
||||
}, 0)
|
||||
|
||||
if vers > ver {
|
||||
if len(needFlushs) < 1 {
|
||||
return
|
||||
}
|
||||
|
||||
r, er := m.batchCacheFn(params...)
|
||||
if err != nil {
|
||||
rr, er := m.batchCacheFn(c, needFlushs, params...)
|
||||
if er != nil {
|
||||
err = er
|
||||
return
|
||||
}
|
||||
for k, v := range r {
|
||||
for k := range needIndex {
|
||||
v, ok := rr[k]
|
||||
if ok {
|
||||
r[k] = v
|
||||
}
|
||||
m.Set(c, k, v)
|
||||
}
|
||||
}
|
||||
@ -178,20 +422,160 @@ func (m *MapCache[K, V]) GetCacheBatch(c context.Context, key []K, timeout time.
|
||||
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 = slice.FilterAndMap(key, func(k K) (V, bool) {
|
||||
return m.data.Get(c, k)
|
||||
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
|
||||
}
|
||||
|
||||
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) {
|
||||
cc := e
|
||||
return func(ctx context.Context, key []K, timeout time.Duration, params ...any) ([]V, error) {
|
||||
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()
|
||||
m.data.ClearExpired(ctx, m.expireTime)
|
||||
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
39
cache/map_test.go
vendored
@ -12,27 +12,22 @@ import (
|
||||
)
|
||||
|
||||
var ca MapCache[string, string]
|
||||
var fn func(a ...any) (string, error)
|
||||
var batchFn func(a ...any) (map[string]string, error)
|
||||
var fn MapSingleFn[string, string]
|
||||
var batchFn MapBatchFn[string, string]
|
||||
var ct context.Context
|
||||
|
||||
func init() {
|
||||
fn = func(a ...any) (string, error) {
|
||||
aa := a[1].(string)
|
||||
fn = func(ctx context.Context, aa string, a ...any) (string, error) {
|
||||
return strings.Repeat(aa, 2), nil
|
||||
}
|
||||
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)
|
||||
arr := a[1].([]string)
|
||||
return slice.FilterAndToMap(arr, func(t string) (string, string, bool) {
|
||||
return slice.FilterAndToMap(arr, func(t string, _ int) (string, string, bool) {
|
||||
return t, strings.Repeat(t, 2), true
|
||||
}), 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) {
|
||||
type args struct {
|
||||
@ -72,7 +67,9 @@ func TestMapCache_Flush(t *testing.T) {
|
||||
m MapCache[K, V]
|
||||
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")
|
||||
tests := []testCase[string, string]{
|
||||
{
|
||||
@ -293,7 +290,7 @@ func TestMapCache_Set(t *testing.T) {
|
||||
|
||||
func TestMapCache_SetCacheBatchFn(t *testing.T) {
|
||||
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 {
|
||||
name string
|
||||
@ -315,19 +312,19 @@ func TestMapCache_SetCacheBatchFn(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMapCache_SetCacheFunc(t *testing.T) {
|
||||
type args[V any] struct {
|
||||
fn func(...any) (V, error)
|
||||
type args[K comparable, V any] struct {
|
||||
fn MapSingleFn[K, V]
|
||||
}
|
||||
type testCase[K comparable, V any] struct {
|
||||
name string
|
||||
m MapCache[K, V]
|
||||
args args[V]
|
||||
args args[K, V]
|
||||
}
|
||||
tests := []testCase[string, string]{
|
||||
{
|
||||
name: "t1",
|
||||
m: ca,
|
||||
args: args[string]{fn: fn},
|
||||
args: args[string, string]{fn: fn},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
@ -355,12 +352,12 @@ func TestMapCache_Ttl(t *testing.T) {
|
||||
name: "t1",
|
||||
m: ca,
|
||||
args: args[string]{ct, "aa"},
|
||||
want: ca.expireTime - tx.Sub(txx),
|
||||
want: ca.GetExpireTime(ct) - tx.Sub(txx),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
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 {
|
||||
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) {
|
||||
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 {
|
||||
name string
|
||||
@ -387,7 +384,7 @@ func TestMapCache_setCacheFn(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
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"))
|
||||
})
|
||||
}
|
||||
|
67
cache/memorymapcache.go
vendored
67
cache/memorymapcache.go
vendored
@ -2,37 +2,22 @@ package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/fthvgb1/wp-go/helper"
|
||||
"github.com/fthvgb1/wp-go/safety"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type MemoryMapCache[K comparable, V any] struct {
|
||||
*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] {
|
||||
return &MapCache[K, V]{
|
||||
data: NewMemoryMapCache[K, V](),
|
||||
cacheFunc: fn,
|
||||
func NewMemoryMapCache[K comparable, V any](expireTime func() time.Duration) *MemoryMapCache[K, V] {
|
||||
return &MemoryMapCache[K, V]{
|
||||
Map: safety.NewMap[K, mapVal[V]](),
|
||||
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 {
|
||||
setTime time.Time
|
||||
@ -40,15 +25,28 @@ type mapVal[T any] struct {
|
||||
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) {
|
||||
v, ok := m.Load(key)
|
||||
if ok {
|
||||
return v.data, true
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
r = v.data
|
||||
t := m.expireTime() - time.Now().Sub(v.setTime)
|
||||
if t <= 0 {
|
||||
ok = false
|
||||
}
|
||||
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)
|
||||
t := time.Now()
|
||||
if ok {
|
||||
@ -65,12 +63,12 @@ func (m *MemoryMapCache[K, V]) Set(_ context.Context, key K, val V, _ time.Durat
|
||||
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)
|
||||
if !ok {
|
||||
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 {
|
||||
@ -85,17 +83,28 @@ func (m *MemoryMapCache[K, V]) Flush(context.Context) {
|
||||
m.Map.Flush()
|
||||
}
|
||||
|
||||
func (m *MemoryMapCache[K, V]) Delete(_ context.Context, key K) {
|
||||
func (m *MemoryMapCache[K, V]) Del(_ context.Context, keys ...K) {
|
||||
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())
|
||||
|
||||
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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
14
cache/memorymapcache_test.go
vendored
14
cache/memorymapcache_test.go
vendored
@ -15,7 +15,7 @@ var ttt time.Time
|
||||
|
||||
func init() {
|
||||
ctx = context.Background()
|
||||
mm = *NewMemoryMapCache[string, string]()
|
||||
mm = *NewMemoryMapCache[string, string](3 * time.Second)
|
||||
ttt = time.Now()
|
||||
mm.Store("aa", mapVal[string]{
|
||||
setTime: ttt,
|
||||
@ -53,7 +53,7 @@ func TestMemoryMapCache_ClearExpired(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
fmt.Println(tt.m)
|
||||
tt.m.ClearExpired(tt.args.in0, tt.args.expire)
|
||||
tt.m.ClearExpired(tt.args.in0)
|
||||
time.Sleep(time.Second)
|
||||
fmt.Println(tt.m)
|
||||
})
|
||||
@ -83,7 +83,7 @@ func TestMemoryMapCache_Delete(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
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"))
|
||||
|
||||
})
|
||||
@ -111,7 +111,7 @@ func TestMemoryMapCache_Flush(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.m.Flush(tt.args.in0)
|
||||
mm.Set(ctx, "aa", "xx", time.Second)
|
||||
mm.Set(ctx, "aa", "xx")
|
||||
fmt.Println(mm.Get(ctx, "aa"))
|
||||
})
|
||||
}
|
||||
@ -180,7 +180,7 @@ func TestMemoryMapCache_Set(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
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))
|
||||
})
|
||||
}
|
||||
@ -209,7 +209,7 @@ func TestMemoryMapCache_Ttl(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
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)
|
||||
}
|
||||
})
|
||||
@ -227,7 +227,7 @@ func TestMemoryMapCache_Ver(t *testing.T) {
|
||||
args args[K]
|
||||
want int
|
||||
}
|
||||
mm.Set(ctx, "aa", "ff", time.Second)
|
||||
mm.Set(ctx, "aa", "ff")
|
||||
tests := []testCase[string, string]{
|
||||
{
|
||||
name: "t1",
|
||||
|
186
cache/pagination.go
vendored
Normal file
186
cache/pagination.go
vendored
Normal 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
544
cache/reload/reload.go
vendored
Normal 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
47
cache/reload/reload_test.go
vendored
Normal 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)
|
||||
})
|
||||
}
|
229
cache/vars.go
vendored
229
cache/vars.go
vendored
@ -2,98 +2,197 @@ package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/fthvgb1/wp-go/helper"
|
||||
"github.com/fthvgb1/wp-go/safety"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
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 {
|
||||
data T
|
||||
mutex *sync.Mutex
|
||||
setCacheFunc func(...any) (T, error)
|
||||
expireTime time.Duration
|
||||
setTime time.Time
|
||||
incr int
|
||||
type IncreaseUpdateVar[T any] struct {
|
||||
CycleTime func() time.Duration
|
||||
Fn IncreaseVarFn[T]
|
||||
}
|
||||
|
||||
func (c *VarCache[T]) GetLastSetTime() time.Time {
|
||||
return c.v.Load().setTime
|
||||
type IncreaseVarFn[T any] func(c context.Context, currentData T, t time.Time, a ...any) (data T, save bool, refresh bool, err error)
|
||||
|
||||
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] {
|
||||
return &VarCache[T]{
|
||||
v: safety.NewVar(vars[T]{
|
||||
mutex: &sync.Mutex{},
|
||||
setCacheFunc: fun,
|
||||
expireTime: duration,
|
||||
}),
|
||||
func initVarCache[T any](t *VarCache[T], a ...any) {
|
||||
gets := helper.ParseArgs[func(AnyCache[T], context.Context) (T, bool)](nil, a...)
|
||||
if gets == nil {
|
||||
t.get = t.AnyCache.Get
|
||||
} else {
|
||||
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 {
|
||||
v := c.v.Load()
|
||||
return time.Duration(v.setTime.UnixNano())+v.expireTime < time.Duration(time.Now().UnixNano())
|
||||
func NewVarCache[T any](cache AnyCache[T], fn func(context.Context, ...any) (T, error), inc *IncreaseUpdateVar[T], ref RefreshVar[T], a ...any) *VarCache[T] {
|
||||
r := &VarCache[T]{
|
||||
AnyCache: cache, setCacheFunc: fn, mutex: sync.Mutex{},
|
||||
increaseUpdate: inc,
|
||||
refresh: ref,
|
||||
}
|
||||
initVarCache(r, a...)
|
||||
return r
|
||||
}
|
||||
|
||||
func (c *VarCache[T]) Flush() {
|
||||
v := c.v.Load()
|
||||
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
|
||||
func (t *VarCache[T]) GetCache(ctx context.Context, timeout time.Duration, params ...any) (T, error) {
|
||||
data, ok := t.Get(ctx)
|
||||
var err error
|
||||
if v.expireTime <= 0 || ((time.Duration(v.setTime.UnixNano()) + v.expireTime) < time.Duration(time.Now().UnixNano())) {
|
||||
t := v.incr
|
||||
call := func() {
|
||||
v.mutex.Lock()
|
||||
defer v.mutex.Unlock()
|
||||
vv := c.v.Load()
|
||||
if vv.incr > t {
|
||||
return
|
||||
if ok {
|
||||
if t.increaseUpdate != nil && t.refresh != nil {
|
||||
nowTime := time.Now()
|
||||
if t.increaseUpdate.CycleTime() > nowTime.Sub(t.GetLastSetTime(ctx)) {
|
||||
return data, nil
|
||||
}
|
||||
r, er := vv.setCacheFunc(params...)
|
||||
if err != nil {
|
||||
fn := func() {
|
||||
t.mutex.Lock()
|
||||
defer t.mutex.Unlock()
|
||||
da, save, refresh, er := t.increaseUpdate.Fn(ctx, data, t.GetLastSetTime(ctx), params...)
|
||||
if er != nil {
|
||||
err = er
|
||||
return
|
||||
}
|
||||
vv.setTime = time.Now()
|
||||
vv.data = r
|
||||
data = r
|
||||
vv.incr++
|
||||
c.v.Store(vv)
|
||||
if save {
|
||||
t.Set(ctx, da)
|
||||
}
|
||||
if refresh {
|
||||
t.refresh.Refresh(ctx, params...)
|
||||
}
|
||||
}
|
||||
if timeout > 0 {
|
||||
ctx, cancel := context.WithTimeout(ctx, timeout)
|
||||
defer cancel()
|
||||
done := make(chan struct{}, 1)
|
||||
go func() {
|
||||
call()
|
||||
done <- struct{}{}
|
||||
close(done)
|
||||
}()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
err = errors.New(fmt.Sprintf("get cache %s", ctx.Err().Error()))
|
||||
case <-done:
|
||||
|
||||
er := helper.RunFnWithTimeout(ctx, timeout, fn, "increaseUpdate cache fail")
|
||||
if err == nil && er != nil {
|
||||
err = er
|
||||
}
|
||||
} else {
|
||||
fn()
|
||||
}
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
call := func() {
|
||||
t.mutex.Lock()
|
||||
defer t.mutex.Unlock()
|
||||
dat, ok := t.Get(ctx)
|
||||
if ok {
|
||||
data = dat
|
||||
return
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
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
41
cache/vars_test.go
vendored
@ -7,9 +7,11 @@ import (
|
||||
"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
|
||||
}, time.Minute)
|
||||
})
|
||||
|
||||
func TestVarCache_Flush(t *testing.T) {
|
||||
type testCase[T any] struct {
|
||||
@ -26,41 +28,8 @@ func TestVarCache_Flush(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
fmt.Println(tt.c.GetCache(c, time.Second))
|
||||
tt.c.Flush()
|
||||
tt.c.Flush(ctx)
|
||||
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
Loading…
Reference in New Issue
Block a user