Merge pull request #15 from fthvgb1/dev

Dev
This commit is contained in:
2023-02-06 22:57:52 +08:00 committed by GitHub
commit d69a04e65d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 588 additions and 456 deletions

View File

@ -27,30 +27,42 @@ ssl:
cert: "" cert: ""
key: "" key: ""
# 最近文章缓存时间 cacheTime:
recentPostCacheTime: 5m # 静态资源缓存时间Cache-Control
# 分类缓存时间 cacheControl: 5d
categoryCacheTime: 5m # 最近文章缓存时间
# 上下篇缓存时间 recentPostCacheTime: 5m
contextPostCacheTime: 10h # 分类缓存时间
# 最近评论缓存时间 categoryCacheTime: 5m
recentCommentsCacheTime: 5m # 上下篇缓存时间
# 摘要缓存时间 contextPostCacheTime: 10h
digestCacheTime: 5m # 最近评论缓存时间
recentCommentsCacheTime: 5m
# 摘要缓存时间
digestCacheTime: 5m
# 文档列表id页缓存 包括默认列表、分类
postListCacheTime: 1h
# 搜索文档id缓存时间
searchPostCacheTime: 5m
# 月归档文章id缓存时间
monthPostCacheTime: 1h
# 文档数据缓存时间
postDataCacheTime: 1h
# 文章评论缓存时间
postCommentsCacheTime: 5m
# 定时清理缓存周期时间
crontabClearCacheTime: 5m
# 文档最大id缓存时间
maxPostIdCacheTime: 1h
# 用户信息缓存时间
userInfoCacheTime: 24h
# 单独评论缓存时间
commentsCacheTime: 24h
# 主题的页眉图片缓存时间
themeHeaderImagCacheTime: 5m
# 摘要字数 # 摘要字数
digestWordCount: 300 digestWordCount: 300
# 文档列表id页缓存 包括默认列表、分类
postListCacheTime: 1h
# 搜索文档id缓存时间
searchPostCacheTime: 5m
# 月归档文章id缓存时间
monthPostCacheTime: 1h
# 文档数据缓存时间
postDataCacheTime: 1h
# 文章评论缓存时间
postCommentsCacheTime: 5m
# 定时清理缓存周期时间
crontabClearCacheTime: 5m
# 到达指定并发请求数时随机sleep # 到达指定并发请求数时随机sleep
maxRequestSleepNum: 100 maxRequestSleepNum: 100
# 随机sleep时间 # 随机sleep时间
@ -59,14 +71,7 @@ sleepTime: [1s,3s]
maxRequestNum: 500 maxRequestNum: 500
# 单ip同时最大搜索请求数 # 单ip同时最大搜索请求数
singleIpSearchNum: 10 singleIpSearchNum: 10
# 文档最大id缓存时间
maxPostIdCacheTime: 1h
# 用户信息缓存时间
userInfoCacheTime: 24h
# 单独评论缓存时间
commentsCacheTime: 24h
# 主题的页眉图片缓存时间
themeHeaderImagCacheTime: 5m
# Gzip # Gzip
gzip: false gzip: false

View File

@ -8,6 +8,13 @@ func ToAny[T any](v T) any {
return v return v
} }
func Or[T any](is bool, left, right T) T {
if is {
return left
}
return right
}
func StructColumnToSlice[T any, M any](arr []M, field string) (r []T) { func StructColumnToSlice[T any, M any](arr []M, field string) (r []T) {
for i := 0; i < len(arr); i++ { for i := 0; i < len(arr); i++ {
v := reflect.ValueOf(arr[i]).FieldByName(field).Interface() v := reflect.ValueOf(arr[i]).FieldByName(field).Interface()

View File

@ -49,6 +49,11 @@ func AnyAnyToStrAny(m map[any]any) (r map[string]any) {
return return
} }
func IsExists[K comparable, V any](m map[K]V, k K) bool {
_, ok := m[k]
return ok
}
func Reduce[T, V any, K comparable](m map[K]V, fn func(K, V, T) T, r T) T { func Reduce[T, V any, K comparable](m map[K]V, fn func(K, V, T) T, r T) T {
for k, v := range m { for k, v := range m {
r = fn(k, v, r) r = fn(k, v, r)

View File

@ -2,6 +2,7 @@ package number
import ( import (
"fmt" "fmt"
"golang.org/x/exp/constraints"
"reflect" "reflect"
"testing" "testing"
) )
@ -195,10 +196,10 @@ func TestRand(t *testing.T) {
} }
func TestAbs(t *testing.T) { func TestAbs(t *testing.T) {
type args[T Number] struct { type args[T constraints.Integer | constraints.Float] struct {
n T n T
} }
type testCase[T Number] struct { type testCase[T constraints.Integer | constraints.Float] struct {
name string name string
args args[T] args args[T]
want T want T

View File

@ -3,7 +3,9 @@ package strings
import ( import (
"crypto/md5" "crypto/md5"
"fmt" "fmt"
"golang.org/x/exp/constraints"
"io" "io"
"strconv"
"strings" "strings"
) )
@ -20,6 +22,17 @@ func Join(s ...string) (str string) {
return return
} }
func ToInteger[T constraints.Integer](s string, defaults T) T {
if s == "" {
return defaults
}
i, err := strconv.ParseInt(s, 10, 64)
if err != nil {
return defaults
}
return T(i)
}
func Md5(str string) string { func Md5(str string) string {
h := md5.New() h := md5.New()
_, err := io.WriteString(h, str) _, err := io.WriteString(h, str)

View File

@ -1,6 +1,9 @@
package strings package strings
import "testing" import (
"golang.org/x/exp/constraints"
"testing"
)
func TestStrJoin(t *testing.T) { func TestStrJoin(t *testing.T) {
type args struct { type args struct {
@ -21,3 +24,32 @@ func TestStrJoin(t *testing.T) {
}) })
} }
} }
func TestToInteger(t *testing.T) {
type args[T constraints.Integer] struct {
s string
z T
}
type testCase[T constraints.Integer] struct {
name string
args args[T]
want T
}
tests := []testCase[int64]{
{
name: "t1",
args: args[int64]{
"10",
0,
},
want: int64(10),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := ToInteger[int64](tt.args.s, tt.args.z); got != tt.want {
t.Errorf("StrToInt() = %v, want %v", got, tt.want)
}
})
}
}

View File

@ -5,6 +5,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/fthvgb1/wp-go/helper/slice" "github.com/fthvgb1/wp-go/helper/slice"
str "github.com/fthvgb1/wp-go/helper/strings"
"github.com/fthvgb1/wp-go/internal/mail" "github.com/fthvgb1/wp-go/internal/mail"
"github.com/fthvgb1/wp-go/internal/pkg/cache" "github.com/fthvgb1/wp-go/internal/pkg/cache"
"github.com/fthvgb1/wp-go/internal/pkg/config" "github.com/fthvgb1/wp-go/internal/pkg/config"
@ -14,7 +15,6 @@ import (
"io" "io"
"net/http" "net/http"
"net/url" "net/url"
"strconv"
"strings" "strings"
"time" "time"
) )
@ -34,6 +34,7 @@ func PostComment(c *gin.Context) {
c.Writer.WriteString(err.Error()) c.Writer.WriteString(err.Error())
} }
}() }()
conf := config.GetConfig()
if err != nil { if err != nil {
return return
} }
@ -43,7 +44,7 @@ func PostComment(c *gin.Context) {
m := c.PostForm("email") m := c.PostForm("email")
comment := c.PostForm("comment") comment := c.PostForm("comment")
c.Request.Body = io.NopCloser(bytes.NewBuffer(data)) c.Request.Body = io.NopCloser(bytes.NewBuffer(data))
req, err := http.NewRequest("POST", config.Conf.Load().PostCommentUrl, strings.NewReader(c.Request.PostForm.Encode())) req, err := http.NewRequest("POST", conf.PostCommentUrl, strings.NewReader(c.Request.PostForm.Encode()))
if err != nil { if err != nil {
return return
} }
@ -68,7 +69,7 @@ func PostComment(c *gin.Context) {
err = er err = er
return return
} }
cu, er := url.Parse(config.Conf.Load().PostCommentUrl) cu, er := url.Parse(conf.PostCommentUrl)
if er != nil { if er != nil {
err = er err = er
return return
@ -91,8 +92,8 @@ func PostComment(c *gin.Context) {
} }
cc := c.Copy() cc := c.Copy()
go func() { go func() {
id, err := strconv.ParseUint(i, 10, 64) id := str.ToInteger[uint64](i, 0)
if err != nil { if id <= 0 {
logs.ErrPrintln(err, "获取文档id", i) logs.ErrPrintln(err, "获取文档id", i)
return return
} }
@ -102,8 +103,8 @@ func PostComment(c *gin.Context) {
return return
} }
su := fmt.Sprintf("%s: %s[%s]发表了评论对文档[%v]的评论", wpconfig.Options.Value("siteurl"), author, m, post.PostTitle) su := fmt.Sprintf("%s: %s[%s]发表了评论对文档[%v]的评论", wpconfig.Options.Value("siteurl"), author, m, post.PostTitle)
err = mail.SendMail([]string{config.Conf.Load().Mail.User}, su, comment) err = mail.SendMail([]string{conf.Mail.User}, su, comment)
logs.ErrPrintln(err, "发送邮件", config.Conf.Load().Mail.User, su, comment) logs.ErrPrintln(err, "发送邮件", conf.Mail.User, su, comment)
}() }()
s, er := io.ReadAll(ress.Body) s, er := io.ReadAll(ress.Body)

View File

@ -2,15 +2,16 @@ package actions
import ( import (
"fmt" "fmt"
str "github.com/fthvgb1/wp-go/helper/strings"
"github.com/fthvgb1/wp-go/internal/pkg/cache" "github.com/fthvgb1/wp-go/internal/pkg/cache"
"github.com/fthvgb1/wp-go/internal/pkg/logs" "github.com/fthvgb1/wp-go/internal/pkg/logs"
"github.com/fthvgb1/wp-go/internal/pkg/models"
"github.com/fthvgb1/wp-go/internal/plugins" "github.com/fthvgb1/wp-go/internal/plugins"
"github.com/fthvgb1/wp-go/internal/theme" "github.com/fthvgb1/wp-go/internal/theme"
"github.com/fthvgb1/wp-go/internal/wpconfig" "github.com/fthvgb1/wp-go/internal/wpconfig"
"github.com/gin-contrib/sessions" "github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"net/http" "net/http"
"strconv"
) )
type detailHandler struct { type detailHandler struct {
@ -19,9 +20,10 @@ type detailHandler struct {
func Detail(c *gin.Context) { func Detail(c *gin.Context) {
var err error var err error
recent := cache.RecentPosts(c, 5) var post models.Posts
recent := cache.RecentPosts(c, 5, true)
archive := cache.Archives(c) archive := cache.Archives(c)
categoryItems := cache.Categories(c) categoryItems := cache.CategoriesTags(c, plugins.Category)
recentComments := cache.RecentComments(c, 5) recentComments := cache.RecentComments(c, 5)
var ginH = gin.H{ var ginH = gin.H{
"title": wpconfig.Options.Value("blogname"), "title": wpconfig.Options.Value("blogname"),
@ -29,6 +31,7 @@ func Detail(c *gin.Context) {
"archives": archive, "archives": archive,
"categories": categoryItems, "categories": categoryItems,
"recentComments": recentComments, "recentComments": recentComments,
"post": post,
} }
isApproveComment := false isApproveComment := false
status := plugins.Ok status := plugins.Ok
@ -47,21 +50,14 @@ func Detail(c *gin.Context) {
t := theme.GetTemplateName() t := theme.GetTemplateName()
theme.Hook(t, code, c, ginH, plugins.Detail, status) theme.Hook(t, code, c, ginH, plugins.Detail, status)
}() }()
id := c.Param("id") ID := str.ToInteger[uint64](c.Param("id"), 0)
Id := 0
if id != "" {
Id, err = strconv.Atoi(id)
if err != nil {
return
}
}
ID := uint64(Id)
maxId, err := cache.GetMaxPostId(c) maxId, err := cache.GetMaxPostId(c)
logs.ErrPrintln(err, "get max post id") logs.ErrPrintln(err, "get max post id")
if ID > maxId || err != nil { if ID > maxId || ID <= 0 || err != nil {
return return
} }
post, err := cache.GetPostById(c, ID) post, err = cache.GetPostById(c, ID)
if post.Id == 0 || err != nil || post.PostStatus != "publish" { if post.Id == 0 || err != nil || post.PostStatus != "publish" {
return return
} }
@ -71,10 +67,13 @@ func Detail(c *gin.Context) {
showComment = true showComment = true
} }
user := cache.GetUserById(c, post.PostAuthor) user := cache.GetUserById(c, post.PostAuthor)
if post.PostPassword != "" {
plugins.PasswordProjectTitle(&post) plugins.PasswordProjectTitle(&post)
if post.PostPassword != "" && pw != post.PostPassword { if pw != post.PostPassword {
plugins.PasswdProjectContent(&post) plugins.PasswdProjectContent(&post)
showComment = false showComment = false
}
} else if s, ok := cache.NewCommentCache().Get(c, c.Request.URL.RawQuery); ok && s != "" && (post.PostPassword == "" || post.PostPassword != "" && pw == post.PostPassword) { } else if s, ok := cache.NewCommentCache().Get(c, c.Request.URL.RawQuery); ok && s != "" && (post.PostPassword == "" || post.PostPassword != "" && pw == post.PostPassword) {
c.Writer.WriteHeader(http.StatusOK) c.Writer.WriteHeader(http.StatusOK)
c.Writer.Header().Set("Content-Type", "text/html; charset=utf-8") c.Writer.Header().Set("Content-Type", "text/html; charset=utf-8")
@ -92,12 +91,7 @@ func Detail(c *gin.Context) {
ginH["post"] = post ginH["post"] = post
ginH["showComment"] = showComment ginH["showComment"] = showComment
ginH["prev"] = prev ginH["prev"] = prev
depth := wpconfig.Options.Value("thread_comments_depth") d := str.ToInteger(wpconfig.Options.Value("thread_comments_depth"), 5)
d, err := strconv.Atoi(depth)
if err != nil {
logs.ErrPrintln(err, "get comment depth ", depth)
d = 5
}
ginH["maxDep"] = d ginH["maxDep"] = d
ginH["next"] = next ginH["next"] = next
ginH["user"] = user ginH["user"] = user

View File

@ -3,6 +3,7 @@ package actions
import ( import (
"errors" "errors"
"fmt" "fmt"
"github.com/fthvgb1/wp-go/helper/maps"
"github.com/fthvgb1/wp-go/helper/number" "github.com/fthvgb1/wp-go/helper/number"
"github.com/fthvgb1/wp-go/helper/slice" "github.com/fthvgb1/wp-go/helper/slice"
str "github.com/fthvgb1/wp-go/helper/strings" str "github.com/fthvgb1/wp-go/helper/strings"
@ -52,13 +53,12 @@ type indexHandle struct {
} }
func newIndexHandle(ctx *gin.Context) *indexHandle { func newIndexHandle(ctx *gin.Context) *indexHandle {
size := wpconfig.Options.Value("posts_per_page") size := str.ToInteger(wpconfig.Options.Value("posts_per_page"), 10)
si, _ := strconv.Atoi(size)
return &indexHandle{ return &indexHandle{
c: ctx, c: ctx,
session: sessions.Default(ctx), session: sessions.Default(ctx),
page: 1, page: 1,
pageSize: si, pageSize: size,
paginationStep: 1, paginationStep: 1,
titleL: wpconfig.Options.Value("blogname"), titleL: wpconfig.Options.Value("blogname"),
titleR: wpconfig.Options.Value("blogdescription"), titleR: wpconfig.Options.Value("blogdescription"),
@ -93,24 +93,20 @@ func (h *indexHandle) getSearchKey() string {
return fmt.Sprintf("action:%s|%s|%s|%s|%s|%s|%d|%d", h.author, h.search, h.orderBy, h.order, h.category, h.categoryType, h.page, h.pageSize) return fmt.Sprintf("action:%s|%s|%s|%s|%s|%s|%d|%d", h.author, h.search, h.orderBy, h.order, h.category, h.categoryType, h.page, h.pageSize)
} }
var orders = []string{"asc", "desc"} var orders = map[string]struct{}{"asc": {}, "desc": {}}
func (h *indexHandle) parseParams() (err error) { func (h *indexHandle) parseParams() (err error) {
h.order = h.c.Query("order") h.order = h.c.Query("order")
if !maps.IsExists(orders, h.order) {
if !slice.IsContained(h.order, orders) { order := config.GetConfig().PostOrder
order := config.Conf.Load().PostOrder
h.order = "asc" h.order = "asc"
if order != "" && slice.IsContained(order, orders) { if order != "" && maps.IsExists(orders, order) {
h.order = order h.order = order
} }
} }
year := h.c.Param("year") year := h.c.Param("year")
if year != "" { if year != "" {
y, er := strconv.Atoi(year) y := str.ToInteger(year, -1)
if er != nil {
return err
}
if y > time.Now().Year() || y <= 1970 { if y > time.Now().Year() || y <= 1970 {
return errors.New(str.Join("year err : ", year)) return errors.New(str.Join("year err : ", year))
} }
@ -120,11 +116,8 @@ func (h *indexHandle) parseParams() (err error) {
} }
month := h.c.Param("month") month := h.c.Param("month")
if month != "" { if month != "" {
m, err := strconv.Atoi(month) m := str.ToInteger(month, -1)
if err != nil { if !maps.IsExists(months, m) {
return err
}
if _, ok := months[m]; !ok {
return errors.New(str.Join("months err ", month)) return errors.New(str.Join("months err ", month))
} }
@ -137,27 +130,26 @@ func (h *indexHandle) parseParams() (err error) {
h.scene = plugins.Archive h.scene = plugins.Archive
} }
category := h.c.Param("category") category := h.c.Param("category")
if category == "" {
category = h.c.Param("tag")
if category != "" { if category != "" {
h.scene = plugins.Tag
allNames := cache.AllTagsNames(h.c)
if _, ok := allNames[category]; !ok {
return errors.New(str.Join("not exists tag ", category))
}
h.categoryType = "post_tag"
h.header = fmt.Sprintf("标签: <span>%s</span>", category)
}
} else {
h.scene = plugins.Category h.scene = plugins.Category
allNames := cache.AllCategoryNames(h.c) if !maps.IsExists(cache.AllCategoryTagsNames(h.c, plugins.Category), category) {
if _, ok := allNames[category]; !ok {
return errors.New(str.Join("not exists category ", category)) return errors.New(str.Join("not exists category ", category))
} }
h.categoryType = "category" h.categoryType = "category"
h.header = fmt.Sprintf("分类: <span>%s</span>", category) h.header = fmt.Sprintf("分类: <span>%s</span>", category)
}
h.category = category h.category = category
}
tag := h.c.Param("tag")
if tag != "" {
h.scene = plugins.Tag
if !maps.IsExists(cache.AllCategoryTagsNames(h.c, plugins.Tag), tag) {
return errors.New(str.Join("not exists tag ", tag))
}
h.categoryType = "post_tag"
h.header = fmt.Sprintf("标签: <span>%s</span>", tag)
h.category = tag
}
username := h.c.Param("author") username := h.c.Param("author")
if username != "" { if username != "" {
allUsername, er := cache.GetAllUsername(h.c) allUsername, er := cache.GetAllUsername(h.c)
@ -165,7 +157,7 @@ func (h *indexHandle) parseParams() (err error) {
err = er err = er
return return
} }
if _, ok := allUsername[username]; !ok { if !maps.IsExists(allUsername, username) {
err = errors.New(str.Join("user ", username, " is not exists")) err = errors.New(str.Join("user ", username, " is not exists"))
return return
} }
@ -179,9 +171,9 @@ func (h *indexHandle) parseParams() (err error) {
"post_author", "=", strconv.FormatUint(user.Id, 10), "int", "post_author", "=", strconv.FormatUint(user.Id, 10), "int",
}) })
} }
if category != "" { if h.category != "" {
h.where = append(h.where, []string{ h.where = append(h.where, []string{
"d.name", category, "d.name", h.category,
}, []string{"taxonomy", h.categoryType}) }, []string{"taxonomy", h.categoryType})
h.join = append(h.join, []string{ h.join = append(h.join, []string{
"a", "left join", "wp_term_relationships b", "a.Id=b.object_id", "a", "left join", "wp_term_relationships b", "a.Id=b.object_id",
@ -190,7 +182,7 @@ func (h *indexHandle) parseParams() (err error) {
}, []string{ }, []string{
"left join", "wp_terms d", "c.term_id=d.term_id", "left join", "wp_terms d", "c.term_id=d.term_id",
}) })
h.setTitleLR(category, wpconfig.Options.Value("blogname")) h.setTitleLR(h.category, wpconfig.Options.Value("blogname"))
} }
s := h.c.Query("s") s := h.c.Query("s")
if s != "" && strings.Replace(s, " ", "", -1) != "" { if s != "" && strings.Replace(s, " ", "", -1) != "" {
@ -206,15 +198,7 @@ func (h *indexHandle) parseParams() (err error) {
h.search = s h.search = s
h.scene = plugins.Search h.scene = plugins.Search
} }
p := h.c.Query("paged") h.page = str.ToInteger(h.c.Param("page"), 1)
if p == "" {
p = h.c.Param("page")
}
if p != "" {
if pa, err := strconv.Atoi(p); err == nil {
h.page = pa
}
}
total := int(atomic.LoadInt64(&dao.TotalRaw)) total := int(atomic.LoadInt64(&dao.TotalRaw))
if total > 0 && total < (h.page-1)*h.pageSize { if total > 0 && total < (h.page-1)*h.pageSize {
h.page = 1 h.page = 1
@ -236,8 +220,8 @@ func Index(c *gin.Context) {
var totalRaw int var totalRaw int
var err error var err error
archive := cache.Archives(c) archive := cache.Archives(c)
recent := cache.RecentPosts(c, 5) recent := cache.RecentPosts(c, 5, true)
categoryItems := cache.Categories(c) categoryItems := cache.CategoriesTags(c, plugins.Category)
recentComments := cache.RecentComments(c, 5) recentComments := cache.RecentComments(c, 5)
ginH := gin.H{ ginH := gin.H{
"err": err, "err": err,
@ -247,6 +231,7 @@ func Index(c *gin.Context) {
"search": h.search, "search": h.search,
"header": h.header, "header": h.header,
"recentComments": recentComments, "recentComments": recentComments,
"posts": posts,
} }
defer func() { defer func() {
code := http.StatusOK code := http.StatusOK
@ -292,18 +277,16 @@ func Index(c *gin.Context) {
pw := h.session.Get("post_password") pw := h.session.Get("post_password")
plug := plugins.NewPostPlugin(c, h.scene) plug := plugins.NewPostPlugin(c, h.scene)
for i, post := range posts { for i, post := range posts {
if post.PostPassword != "" {
plugins.PasswordProjectTitle(&posts[i]) plugins.PasswordProjectTitle(&posts[i])
if post.PostPassword != "" && pw != post.PostPassword { if pw != post.PostPassword {
plugins.PasswdProjectContent(&posts[i]) plugins.PasswdProjectContent(&posts[i])
}
} else { } else {
plugins.ApplyPlugin(plug, &posts[i]) plugins.ApplyPlugin(plug, &posts[i])
} }
} }
for i, post := range recent {
if post.PostPassword != "" && pw != post.PostPassword {
plugins.PasswdProjectContent(&recent[i])
}
}
q := c.Request.URL.Query().Encode() q := c.Request.URL.Query().Encode()
if q != "" { if q != "" {
q = fmt.Sprintf("?%s", q) q = fmt.Sprintf("?%s", q)

View File

@ -55,11 +55,11 @@ func initConf(c string) (err error) {
return return
} }
err = db.InitDb() database, err := db.InitDb()
if err != nil { if err != nil {
return return
} }
model.InitDB(db.NewSqlxDb(db.Db)) model.InitDB(model.NewSqlxQuery(database))
err = wpconfig.InitOptions() err = wpconfig.InitOptions()
if err != nil { if err != nil {
return return
@ -72,7 +72,7 @@ func initConf(c string) (err error) {
} }
func cronClearCache() { func cronClearCache() {
t := time.NewTicker(config.Conf.Load().CrontabClearCacheTime) t := time.NewTicker(config.GetConfig().CacheTime.CrontabClearCacheTime)
for { for {
select { select {
case <-t.C: case <-t.C:
@ -85,7 +85,7 @@ func cronClearCache() {
func flushCache() { func flushCache() {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
err := mail.SendMail([]string{config.Conf.Load().Mail.User}, "清空缓存失败", fmt.Sprintf("err:[%s]", r)) err := mail.SendMail([]string{config.GetConfig().Mail.User}, "清空缓存失败", fmt.Sprintf("err:[%s]", r))
logs.ErrPrintln(err, "发邮件失败") logs.ErrPrintln(err, "发邮件失败")
} }
}() }()
@ -129,7 +129,7 @@ func signalNotify() {
func main() { func main() {
go signalNotify() go signalNotify()
Gin, reloadFn := route.SetupRouter() Gin, reloadFn := route.SetupRouter()
c := config.Conf.Load() c := config.GetConfig()
middleWareReloadFn = reloadFn middleWareReloadFn = reloadFn
if c.Ssl.Key != "" && c.Ssl.Cert != "" { if c.Ssl.Key != "" && c.Ssl.Cert != "" {
err := Gin.RunTLS(address, c.Ssl.Cert, c.Ssl.Key) err := Gin.RunTLS(address, c.Ssl.Cert, c.Ssl.Key)

View File

@ -18,7 +18,7 @@ func SetupRouter() (*gin.Engine, func()) {
// Disable Console Color // Disable Console Color
// gin.DisableConsoleColor() // gin.DisableConsoleColor()
r := gin.New() r := gin.New()
c := config.Conf.Load() c := config.GetConfig()
if len(c.TrustIps) > 0 { if len(c.TrustIps) > 0 {
err := r.SetTrustedProxies(c.TrustIps) err := r.SetTrustedProxies(c.TrustIps)
if err != nil { if err != nil {
@ -29,7 +29,7 @@ func SetupRouter() (*gin.Engine, func()) {
r.HTMLRender = theme.GetTemplate() r.HTMLRender = theme.GetTemplate()
validServerName, reloadValidServerNameFn := middleware.ValidateServerNames() validServerName, reloadValidServerNameFn := middleware.ValidateServerNames()
fl, flReload := middleware.FlowLimit(c.MaxRequestSleepNum, c.MaxRequestNum, c.SleepTime) fl, flReload := middleware.FlowLimit(c.MaxRequestSleepNum, c.MaxRequestNum, c.CacheTime.SleepTime)
r.Use( r.Use(
gin.Logger(), gin.Logger(),
validServerName, validServerName,
@ -76,15 +76,15 @@ func SetupRouter() (*gin.Engine, func()) {
r.GET("/p/:id/feed", actions.PostFeed) r.GET("/p/:id/feed", actions.PostFeed)
r.GET("/feed", actions.Feed) r.GET("/feed", actions.Feed)
r.GET("/comments/feed", actions.CommentsFeed) r.GET("/comments/feed", actions.CommentsFeed)
cfl, _ := middleware.FlowLimit(c.MaxRequestSleepNum, 5, c.SleepTime) cfl, _ := middleware.FlowLimit(c.MaxRequestSleepNum, 5, c.CacheTime.SleepTime)
r.POST("/comment", cfl, actions.PostComment) r.POST("/comment", cfl, actions.PostComment)
if c.Pprof != "" { if c.Pprof != "" {
pprof.Register(r, c.Pprof) pprof.Register(r, c.Pprof)
} }
fn := func() { fn := func() {
reloadValidServerNameFn() reloadValidServerNameFn()
c := config.Conf.Load() c := config.GetConfig()
flReload(c.MaxRequestSleepNum, c.MaxRequestNum, c.SleepTime) flReload(c.MaxRequestSleepNum, c.MaxRequestNum, c.CacheTime.SleepTime)
slRload(c.SingleIpSearchNum) slRload(c.SingleIpSearchNum)
} }
return r, fn return r, fn

View File

@ -18,7 +18,7 @@ func SendMail(mailTo []string, subject string, body string, files ...string) err
m := gomail.NewMessage( m := gomail.NewMessage(
gomail.SetEncoding(gomail.Base64), gomail.SetEncoding(gomail.Base64),
) )
c := config.Conf.Load() c := config.GetConfig()
m.SetHeader("From", m.SetHeader("From",
m.FormatAddress(c.Mail.User, m.FormatAddress(c.Mail.User,
c.Mail.Alias, c.Mail.Alias,

View File

@ -43,7 +43,7 @@ func RecoverAndSendMail(w io.Writer) func(ctx *gin.Context) {
) )
er := mail.SendMail( er := mail.SendMail(
[]string{config.Conf.Load().Mail.User}, []string{config.GetConfig().Mail.User},
fmt.Sprintf("%s%s %s 发生错误", fmt.Sprintf(wpconfig.Options.Value("siteurl")), c.FullPath(), time.Now().Format(time.RFC1123Z)), content) fmt.Sprintf("%s%s %s 发生错误", fmt.Sprintf(wpconfig.Options.Value("siteurl")), c.FullPath(), time.Now().Format(time.RFC1123Z)), content)
if er != nil { if er != nil {

View File

@ -1,15 +1,26 @@
package middleware package middleware
import ( import (
"github.com/fthvgb1/wp-go/helper/slice" "fmt"
"github.com/fthvgb1/wp-go/internal/pkg/config"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"strings" "strings"
) )
var path = map[string]struct{}{
"wp-includes": {},
"wp-content": {},
"favicon.ico": {},
}
func SetStaticFileCache(c *gin.Context) { func SetStaticFileCache(c *gin.Context) {
f := strings.Split(strings.TrimLeft(c.FullPath(), "/"), "/") f := strings.Split(strings.TrimLeft(c.FullPath(), "/"), "/")
if len(f) > 0 && slice.IsContained(f[0], []string{"wp-includes", "wp-content", "favicon.ico"}) { if _, ok := path[f[0]]; ok {
c.Header("Cache-Control", "private, max-age=86400") t := config.GetConfig().CacheTime.CacheControl
if t > 0 {
c.Header("Cache-Control", fmt.Sprintf("private, max-age=%d", int(t.Seconds())))
} }
}
c.Next() c.Next()
} }

View File

@ -11,7 +11,7 @@ import (
func ValidateServerNames() (func(ctx *gin.Context), func()) { func ValidateServerNames() (func(ctx *gin.Context), func()) {
var serverName safety.Map[string, struct{}] var serverName safety.Map[string, struct{}]
fn := func() { fn := func() {
r := config.Conf.Load().TrustServerNames r := config.GetConfig().TrustServerNames
if len(r) > 0 { if len(r) > 0 {
for _, name := range r { for _, name := range r {
serverName.Store(name, struct{}{}) serverName.Store(name, struct{}{})

View File

@ -3,11 +3,13 @@ package cache
import ( import (
"context" "context"
"github.com/fthvgb1/wp-go/cache" "github.com/fthvgb1/wp-go/cache"
"github.com/fthvgb1/wp-go/helper"
"github.com/fthvgb1/wp-go/helper/slice" "github.com/fthvgb1/wp-go/helper/slice"
"github.com/fthvgb1/wp-go/internal/pkg/config" "github.com/fthvgb1/wp-go/internal/pkg/config"
"github.com/fthvgb1/wp-go/internal/pkg/dao" "github.com/fthvgb1/wp-go/internal/pkg/dao"
"github.com/fthvgb1/wp-go/internal/pkg/logs" "github.com/fthvgb1/wp-go/internal/pkg/logs"
"github.com/fthvgb1/wp-go/internal/pkg/models" "github.com/fthvgb1/wp-go/internal/pkg/models"
"github.com/fthvgb1/wp-go/internal/plugins"
"sync" "sync"
"time" "time"
) )
@ -46,39 +48,43 @@ var headerImagesCache *cache.MapCache[string, []models.PostThumbnail]
var ctx context.Context var ctx context.Context
func InitActionsCommonCache() { func InitActionsCommonCache() {
c := config.Conf.Load() c := config.GetConfig()
archivesCaches = &Arch{ archivesCaches = &Arch{
mutex: &sync.Mutex{}, mutex: &sync.Mutex{},
setCacheFunc: dao.Archives, fn: dao.Archives,
} }
searchPostIdsCache = cache.NewMemoryMapCacheByFn[string](dao.SearchPostIds, c.SearchPostCacheTime) searchPostIdsCache = cache.NewMemoryMapCacheByFn[string](dao.SearchPostIds, c.CacheTime.SearchPostCacheTime)
postListIdsCache = cache.NewMemoryMapCacheByFn[string](dao.SearchPostIds, c.PostListCacheTime) postListIdsCache = cache.NewMemoryMapCacheByFn[string](dao.SearchPostIds, c.CacheTime.PostListCacheTime)
monthPostsCache = cache.NewMemoryMapCacheByFn[string](dao.MonthPost, c.MonthPostCacheTime) monthPostsCache = cache.NewMemoryMapCacheByFn[string](dao.MonthPost, c.CacheTime.MonthPostCacheTime)
postContextCache = cache.NewMemoryMapCacheByFn[uint64](dao.GetPostContext, c.ContextPostCacheTime) postContextCache = cache.NewMemoryMapCacheByFn[uint64](dao.GetPostContext, c.CacheTime.ContextPostCacheTime)
postsCache = cache.NewMemoryMapCacheByBatchFn(dao.GetPostsByIds, c.PostDataCacheTime) postsCache = cache.NewMemoryMapCacheByBatchFn(dao.GetPostsByIds, c.CacheTime.PostDataCacheTime)
postMetaCache = cache.NewMemoryMapCacheByBatchFn(dao.GetPostMetaByPostIds, c.PostDataCacheTime) postMetaCache = cache.NewMemoryMapCacheByBatchFn(dao.GetPostMetaByPostIds, c.CacheTime.PostDataCacheTime)
categoryAndTagsCaches = cache.NewVarCache(dao.CategoriesAndTags, c.CategoryCacheTime) categoryAndTagsCaches = cache.NewVarCache(dao.CategoriesAndTags, c.CacheTime.CategoryCacheTime)
recentPostsCaches = cache.NewVarCache(dao.RecentPosts, c.RecentPostCacheTime) recentPostsCaches = cache.NewVarCache(dao.RecentPosts, c.CacheTime.RecentPostCacheTime)
recentCommentsCaches = cache.NewVarCache(dao.RecentComments, c.RecentCommentsCacheTime) recentCommentsCaches = cache.NewVarCache(dao.RecentComments, c.CacheTime.RecentCommentsCacheTime)
postCommentCaches = cache.NewMemoryMapCacheByFn[uint64](dao.PostComments, c.PostCommentsCacheTime) postCommentCaches = cache.NewMemoryMapCacheByFn[uint64](dao.PostComments, c.CacheTime.PostCommentsCacheTime)
maxPostIdCache = cache.NewVarCache(dao.GetMaxPostId, c.MaxPostIdCacheTime) maxPostIdCache = cache.NewVarCache(dao.GetMaxPostId, c.CacheTime.MaxPostIdCacheTime)
usersCache = cache.NewMemoryMapCacheByFn[uint64](dao.GetUserById, c.UserInfoCacheTime) usersCache = cache.NewMemoryMapCacheByFn[uint64](dao.GetUserById, c.CacheTime.UserInfoCacheTime)
usersNameCache = cache.NewMemoryMapCacheByFn[string](dao.GetUserByName, c.UserInfoCacheTime) usersNameCache = cache.NewMemoryMapCacheByFn[string](dao.GetUserByName, c.CacheTime.UserInfoCacheTime)
commentsCache = cache.NewMemoryMapCacheByBatchFn(dao.GetCommentByIds, c.CommentsCacheTime) commentsCache = cache.NewMemoryMapCacheByBatchFn(dao.GetCommentByIds, c.CacheTime.CommentsCacheTime)
allUsernameCache = cache.NewVarCache(dao.AllUsername, c.CacheTime.UserInfoCacheTime)
headerImagesCache = cache.NewMemoryMapCacheByFn[string](getHeaderImages, c.CacheTime.ThemeHeaderImagCacheTime)
feedCache = cache.NewVarCache(feed, time.Hour) feedCache = cache.NewVarCache(feed, time.Hour)
@ -88,10 +94,6 @@ func InitActionsCommonCache() {
newCommentCache = cache.NewMemoryMapCacheByFn[string, string](nil, 15*time.Minute) newCommentCache = cache.NewMemoryMapCacheByFn[string, string](nil, 15*time.Minute)
allUsernameCache = cache.NewVarCache(dao.AllUsername, c.UserInfoCacheTime)
headerImagesCache = cache.NewMemoryMapCacheByFn[string](getHeaderImages, c.ThemeHeaderImagCacheTime)
ctx = context.Background() ctx = context.Background()
InitFeed() InitFeed()
@ -134,60 +136,42 @@ func Archives(ctx context.Context) (r []models.PostArchive) {
type Arch struct { type Arch struct {
data []models.PostArchive data []models.PostArchive
mutex *sync.Mutex mutex *sync.Mutex
setCacheFunc func(context.Context) ([]models.PostArchive, error) fn func(context.Context) ([]models.PostArchive, error)
month time.Month month time.Month
} }
func (c *Arch) getArchiveCache(ctx context.Context) []models.PostArchive { func (a *Arch) getArchiveCache(ctx context.Context) []models.PostArchive {
l := len(c.data) l := len(a.data)
m := time.Now().Month() m := time.Now().Month()
if l > 0 && c.month != m || l < 1 { if l > 0 && a.month != m || l < 1 {
r, err := c.setCacheFunc(ctx) r, err := a.fn(ctx)
if err != nil { if err != nil {
logs.ErrPrintln(err, "set cache err[%s]") logs.ErrPrintln(err, "set cache err[%s]")
return nil return nil
} }
c.mutex.Lock() a.mutex.Lock()
defer c.mutex.Unlock() defer a.mutex.Unlock()
c.month = m a.month = m
c.data = r a.data = r
} }
return c.data return a.data
} }
func Categories(ctx context.Context) []models.TermsMy { func CategoriesTags(ctx context.Context, t ...int) []models.TermsMy {
r, err := categoryAndTagsCaches.GetCache(ctx, time.Second, ctx) r, err := categoryAndTagsCaches.GetCache(ctx, time.Second, ctx)
logs.ErrPrintln(err, "get category err") logs.ErrPrintln(err, "get category err")
r = slice.Filter(r, func(my models.TermsMy) bool { if len(t) > 0 {
return my.Taxonomy == "category" return slice.Filter(r, func(my models.TermsMy) bool {
return helper.Or(t[0] == plugins.Tag, "post_tag", "category") == my.Taxonomy
}) })
}
return r return r
} }
func AllCategoryTagsNames(ctx context.Context, c int) map[string]struct{} {
func Tags(ctx context.Context) []models.TermsMy {
r, err := categoryAndTagsCaches.GetCache(ctx, time.Second, ctx)
logs.ErrPrintln(err, "get category err")
r = slice.Filter(r, func(my models.TermsMy) bool {
return my.Taxonomy == "post_tag"
})
return r
}
func AllTagsNames(ctx context.Context) map[string]struct{} {
r, err := categoryAndTagsCaches.GetCache(ctx, time.Second, ctx) r, err := categoryAndTagsCaches.GetCache(ctx, time.Second, ctx)
logs.ErrPrintln(err, "get category err") logs.ErrPrintln(err, "get category err")
return slice.FilterAndToMap(r, func(t models.TermsMy) (string, struct{}, bool) { return slice.FilterAndToMap(r, func(t models.TermsMy) (string, struct{}, bool) {
if t.Taxonomy == "post_tag" { if helper.Or(c == plugins.Tag, "post_tag", "category") == t.Taxonomy {
return t.Name, struct{}{}, true
}
return "", struct{}{}, false
})
}
func AllCategoryNames(ctx context.Context) map[string]struct{} {
r, err := categoryAndTagsCaches.GetCache(ctx, time.Second, ctx)
logs.ErrPrintln(err, "get category err")
return slice.FilterAndToMap(r, func(t models.TermsMy) (string, struct{}, bool) {
if t.Taxonomy == "category" {
return t.Name, struct{}{}, true return t.Name, struct{}{}, true
} }
return "", struct{}{}, false return "", struct{}{}, false

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"github.com/fthvgb1/wp-go/cache" "github.com/fthvgb1/wp-go/cache"
"github.com/fthvgb1/wp-go/helper/slice" "github.com/fthvgb1/wp-go/helper/slice"
str "github.com/fthvgb1/wp-go/helper/strings"
"github.com/fthvgb1/wp-go/internal/pkg/logs" "github.com/fthvgb1/wp-go/internal/pkg/logs"
"github.com/fthvgb1/wp-go/internal/pkg/models" "github.com/fthvgb1/wp-go/internal/pkg/models"
"github.com/fthvgb1/wp-go/internal/plugins" "github.com/fthvgb1/wp-go/internal/plugins"
@ -11,7 +12,6 @@ import (
"github.com/fthvgb1/wp-go/plugin/digest" "github.com/fthvgb1/wp-go/plugin/digest"
"github.com/fthvgb1/wp-go/rss2" "github.com/fthvgb1/wp-go/rss2"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"strconv"
"strings" "strings"
"time" "time"
) )
@ -46,7 +46,7 @@ func PostFeedCache() *cache.MapCache[string, string] {
func feed(arg ...any) (xml []string, err error) { func feed(arg ...any) (xml []string, err error) {
c := arg[0].(*gin.Context) c := arg[0].(*gin.Context)
r := RecentPosts(c, 10) r := RecentPosts(c, 10, true)
ids := slice.Map(r, func(t models.Posts) uint64 { ids := slice.Map(r, func(t models.Posts) uint64 {
return t.Id return t.Id
}) })
@ -54,21 +54,22 @@ func feed(arg ...any) (xml []string, err error) {
if err != nil { if err != nil {
return return
} }
site := wpconfig.Options.Value("siteurl")
rs := templateRss rs := templateRss
rs.LastBuildDate = time.Now().Format(timeFormat) rs.LastBuildDate = time.Now().Format(timeFormat)
rs.Items = slice.Map(posts, func(t models.Posts) rss2.Item { rs.Items = slice.Map(posts, func(t models.Posts) rss2.Item {
desc := "无法提供摘要。这是一篇受保护的文章。" desc := "无法提供摘要。这是一篇受保护的文章。"
plugins.PasswordProjectTitle(&t)
if t.PostPassword != "" { if t.PostPassword != "" {
plugins.PasswordProjectTitle(&t)
plugins.PasswdProjectContent(&t) plugins.PasswdProjectContent(&t)
} else { } else {
desc = digest.Raw(t.PostContent, 55, fmt.Sprintf("/p/%d", t.Id)) desc = digest.Raw(t.PostContent, 55, fmt.Sprintf("/p/%d", t.Id))
} }
l := "" l := ""
if t.CommentStatus == "open" && t.CommentCount > 0 { if t.CommentStatus == "open" && t.CommentCount > 0 {
l = fmt.Sprintf("%s/p/%d#comments", wpconfig.Options.Value("siteurl"), t.Id) l = fmt.Sprintf("%s/p/%d#comments", site, t.Id)
} else if t.CommentStatus == "open" && t.CommentCount == 0 { } else if t.CommentStatus == "open" && t.CommentCount == 0 {
l = fmt.Sprintf("%s/p/%d#respond", wpconfig.Options.Value("siteurl"), t.Id) l = fmt.Sprintf("%s/p/%d#respond", site, t.Id)
} }
user := GetUserById(c, t.PostAuthor) user := GetUserById(c, t.PostAuthor)
@ -80,8 +81,8 @@ func feed(arg ...any) (xml []string, err error) {
Content: t.PostContent, Content: t.PostContent,
Category: strings.Join(t.Categories, "、"), Category: strings.Join(t.Categories, "、"),
CommentLink: l, CommentLink: l,
CommentRss: fmt.Sprintf("%s/p/%d/feed", wpconfig.Options.Value("siteurl"), t.Id), CommentRss: fmt.Sprintf("%s/p/%d/feed", site, t.Id),
Link: fmt.Sprintf("%s/p/%d", wpconfig.Options.Value("siteurl"), t.Id), Link: fmt.Sprintf("%s/p/%d", site, t.Id),
Description: desc, Description: desc,
PubDate: t.PostDateGmt.Format(timeFormat), PubDate: t.PostDateGmt.Format(timeFormat),
} }
@ -93,42 +94,36 @@ func feed(arg ...any) (xml []string, err error) {
func postFeed(arg ...any) (x string, err error) { func postFeed(arg ...any) (x string, err error) {
c := arg[0].(*gin.Context) c := arg[0].(*gin.Context)
id := arg[1].(string) id := arg[1].(string)
Id := 0 ID := str.ToInteger[uint64](id, 0)
if id != "" {
Id, err = strconv.Atoi(id)
if err != nil {
return
}
}
ID := uint64(Id)
maxId, err := GetMaxPostId(c) maxId, err := GetMaxPostId(c)
logs.ErrPrintln(err, "get max post id") logs.ErrPrintln(err, "get max post id")
if ID > maxId || err != nil { if ID < 1 || ID > maxId || err != nil {
return return
} }
post, err := GetPostById(c, ID) post, err := GetPostById(c, ID)
if post.Id == 0 || err != nil { if post.Id == 0 || err != nil {
return return
} }
plugins.PasswordProjectTitle(&post)
comments, err := PostComments(c, post.Id) comments, err := PostComments(c, post.Id)
if err != nil { if err != nil {
return return
} }
rs := templateRss rs := templateRss
site := wpconfig.Options.Value("siteurl")
rs.Title = fmt.Sprintf("《%s》的评论", post.PostTitle) rs.Title = fmt.Sprintf("《%s》的评论", post.PostTitle)
rs.AtomLink = fmt.Sprintf("%s/p/%d/feed", wpconfig.Options.Value("siteurl"), post.Id) rs.AtomLink = fmt.Sprintf("%s/p/%d/feed", site, post.Id)
rs.Link = fmt.Sprintf("%s/p/%d", wpconfig.Options.Value("siteurl"), post.Id) rs.Link = fmt.Sprintf("%s/p/%d", site, post.Id)
rs.LastBuildDate = time.Now().Format(timeFormat) rs.LastBuildDate = time.Now().Format(timeFormat)
if post.PostPassword != "" { if post.PostPassword != "" {
if len(comments) > 0 { plugins.PasswordProjectTitle(&post)
plugins.PasswdProjectContent(&post) plugins.PasswdProjectContent(&post)
if len(comments) > 0 {
t := comments[len(comments)-1] t := comments[len(comments)-1]
rs.Items = []rss2.Item{ rs.Items = []rss2.Item{
{ {
Title: fmt.Sprintf("评价者:%s", t.CommentAuthor), Title: fmt.Sprintf("评价者:%s", t.CommentAuthor),
Link: fmt.Sprintf("%s/p/%d#comment-%d", wpconfig.Options.Value("siteurl"), post.Id, t.CommentId), Link: fmt.Sprintf("%s/p/%d#comment-%d", site, post.Id, t.CommentId),
Creator: t.CommentAuthor, Creator: t.CommentAuthor,
PubDate: t.CommentDateGmt.Format(timeFormat), PubDate: t.CommentDateGmt.Format(timeFormat),
Guid: fmt.Sprintf("%s#comment-%d", post.Guid, t.CommentId), Guid: fmt.Sprintf("%s#comment-%d", post.Guid, t.CommentId),
@ -141,7 +136,7 @@ func postFeed(arg ...any) (x string, err error) {
rs.Items = slice.Map(comments, func(t models.Comments) rss2.Item { rs.Items = slice.Map(comments, func(t models.Comments) rss2.Item {
return rss2.Item{ return rss2.Item{
Title: fmt.Sprintf("评价者:%s", t.CommentAuthor), Title: fmt.Sprintf("评价者:%s", t.CommentAuthor),
Link: fmt.Sprintf("%s/p/%d#comment-%d", wpconfig.Options.Value("siteurl"), post.Id, t.CommentId), Link: fmt.Sprintf("%s/p/%d#comment-%d", site, post.Id, t.CommentId),
Creator: t.CommentAuthor, Creator: t.CommentAuthor,
PubDate: t.CommentDateGmt.Format(timeFormat), PubDate: t.CommentDateGmt.Format(timeFormat),
Guid: fmt.Sprintf("%s#comment-%d", post.Guid, t.CommentId), Guid: fmt.Sprintf("%s#comment-%d", post.Guid, t.CommentId),
@ -160,7 +155,8 @@ func commentsFeed(args ...any) (r []string, err error) {
rs := templateRss rs := templateRss
rs.Title = fmt.Sprintf("\"%s\"的评论", wpconfig.Options.Value("blogname")) rs.Title = fmt.Sprintf("\"%s\"的评论", wpconfig.Options.Value("blogname"))
rs.LastBuildDate = time.Now().Format(timeFormat) rs.LastBuildDate = time.Now().Format(timeFormat)
rs.AtomLink = fmt.Sprintf("%s/comments/feed", wpconfig.Options.Value("siteurl")) site := wpconfig.Options.Value("siteurl")
rs.AtomLink = fmt.Sprintf("%s/comments/feed", site)
com, err := GetCommentByIds(c, slice.Map(commens, func(t models.Comments) uint64 { com, err := GetCommentByIds(c, slice.Map(commens, func(t models.Comments) uint64 {
return t.CommentId return t.CommentId
})) }))
@ -169,10 +165,10 @@ func commentsFeed(args ...any) (r []string, err error) {
} }
rs.Items = slice.Map(com, func(t models.Comments) rss2.Item { rs.Items = slice.Map(com, func(t models.Comments) rss2.Item {
post, _ := GetPostById(c, t.CommentPostId) post, _ := GetPostById(c, t.CommentPostId)
plugins.PasswordProjectTitle(&post)
desc := "评论受保护:要查看请输入密码。" desc := "评论受保护:要查看请输入密码。"
content := t.CommentContent content := t.CommentContent
if post.PostPassword != "" { if post.PostPassword != "" {
plugins.PasswordProjectTitle(&post)
plugins.PasswdProjectContent(&post) plugins.PasswdProjectContent(&post)
content = post.PostContent content = post.PostContent
} else { } else {
@ -181,7 +177,7 @@ func commentsFeed(args ...any) (r []string, err error) {
} }
return rss2.Item{ return rss2.Item{
Title: fmt.Sprintf("%s对《%s》的评论", t.CommentAuthor, post.PostTitle), Title: fmt.Sprintf("%s对《%s》的评论", t.CommentAuthor, post.PostTitle),
Link: fmt.Sprintf("%s/p/%d#comment-%d", wpconfig.Options.Value("siteurl"), post.Id, t.CommentId), Link: fmt.Sprintf("%s/p/%d#comment-%d", site, post.Id, t.CommentId),
Creator: t.CommentAuthor, Creator: t.CommentAuthor,
Description: desc, Description: desc,
PubDate: t.CommentDateGmt.Format(timeFormat), PubDate: t.CommentDateGmt.Format(timeFormat),

View File

@ -6,6 +6,7 @@ import (
"github.com/fthvgb1/wp-go/helper/slice" "github.com/fthvgb1/wp-go/helper/slice"
"github.com/fthvgb1/wp-go/internal/pkg/logs" "github.com/fthvgb1/wp-go/internal/pkg/logs"
"github.com/fthvgb1/wp-go/internal/pkg/models" "github.com/fthvgb1/wp-go/internal/pkg/models"
"github.com/fthvgb1/wp-go/internal/plugins"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"time" "time"
) )
@ -42,11 +43,23 @@ func GetMaxPostId(ctx *gin.Context) (uint64, error) {
return maxPostIdCache.GetCache(ctx, time.Second, ctx) return maxPostIdCache.GetCache(ctx, time.Second, ctx)
} }
func RecentPosts(ctx context.Context, n int) (r []models.Posts) { func RecentPosts(ctx context.Context, n int, project bool) (r []models.Posts) {
r, err := recentPostsCaches.GetCache(ctx, time.Second, ctx) nn := n
if nn <= 5 {
nn = 10
}
r, err := recentPostsCaches.GetCache(ctx, time.Second, ctx, nn)
if n < len(r) { if n < len(r) {
r = r[:n] r = r[:n]
} }
if project {
r = slice.Map(r, func(t models.Posts) models.Posts {
if t.PostPassword != "" {
plugins.PasswordProjectTitle(&t)
}
return t
})
}
logs.ErrPrintln(err, "get recent post") logs.ErrPrintln(err, "get recent post")
return return
} }

View File

@ -8,33 +8,21 @@ import (
"time" "time"
) )
var Conf safety.Var[Config] var config safety.Var[Config]
func GetConfig() Config {
return config.Load()
}
type Config struct { type Config struct {
Ssl Ssl `yaml:"ssl"` Ssl Ssl `yaml:"ssl"`
Mysql Mysql `yaml:"mysql"` Mysql Mysql `yaml:"mysql"`
Mail Mail `yaml:"mail"` Mail Mail `yaml:"mail"`
RecentPostCacheTime time.Duration `yaml:"recentPostCacheTime"` CacheTime CacheTime `yaml:"cacheTime"`
CategoryCacheTime time.Duration `yaml:"categoryCacheTime"`
ArchiveCacheTime time.Duration `yaml:"archiveCacheTime"`
ContextPostCacheTime time.Duration `yaml:"contextPostCacheTime"`
RecentCommentsCacheTime time.Duration `yaml:"recentCommentsCacheTime"`
DigestCacheTime time.Duration `yaml:"digestCacheTime"`
DigestWordCount int `yaml:"digestWordCount"` DigestWordCount int `yaml:"digestWordCount"`
PostListCacheTime time.Duration `yaml:"postListCacheTime"`
SearchPostCacheTime time.Duration `yaml:"searchPostCacheTime"`
MonthPostCacheTime time.Duration `yaml:"monthPostCacheTime"`
PostDataCacheTime time.Duration `yaml:"postDataCacheTime"`
PostCommentsCacheTime time.Duration `yaml:"postCommentsCacheTime"`
CrontabClearCacheTime time.Duration `yaml:"crontabClearCacheTime"`
MaxRequestSleepNum int64 `yaml:"maxRequestSleepNum"` MaxRequestSleepNum int64 `yaml:"maxRequestSleepNum"`
SleepTime []time.Duration `yaml:"sleepTime"`
MaxRequestNum int64 `yaml:"maxRequestNum"` MaxRequestNum int64 `yaml:"maxRequestNum"`
SingleIpSearchNum int64 `yaml:"singleIpSearchNum"` SingleIpSearchNum int64 `yaml:"singleIpSearchNum"`
MaxPostIdCacheTime time.Duration `yaml:"maxPostIdCacheTime"`
UserInfoCacheTime time.Duration `yaml:"userInfoCacheTime"`
CommentsCacheTime time.Duration `yaml:"commentsCacheTime"`
ThemeHeaderImagCacheTime time.Duration `yaml:"themeHeaderImagCacheTime"`
Gzip bool `yaml:"gzip"` Gzip bool `yaml:"gzip"`
PostCommentUrl string `yaml:"postCommentUrl"` PostCommentUrl string `yaml:"postCommentUrl"`
TrustIps []string `yaml:"trustIps"` TrustIps []string `yaml:"trustIps"`
@ -45,6 +33,27 @@ type Config struct {
Pprof string `yaml:"pprof"` Pprof string `yaml:"pprof"`
} }
type CacheTime struct {
CacheControl time.Duration `yaml:"cacheControl"`
RecentPostCacheTime time.Duration `yaml:"recentPostCacheTime"`
CategoryCacheTime time.Duration `yaml:"categoryCacheTime"`
ArchiveCacheTime time.Duration `yaml:"archiveCacheTime"`
ContextPostCacheTime time.Duration `yaml:"contextPostCacheTime"`
RecentCommentsCacheTime time.Duration `yaml:"recentCommentsCacheTime"`
DigestCacheTime time.Duration `yaml:"digestCacheTime"`
PostListCacheTime time.Duration `yaml:"postListCacheTime"`
SearchPostCacheTime time.Duration `yaml:"searchPostCacheTime"`
MonthPostCacheTime time.Duration `yaml:"monthPostCacheTime"`
PostDataCacheTime time.Duration `yaml:"postDataCacheTime"`
PostCommentsCacheTime time.Duration `yaml:"postCommentsCacheTime"`
CrontabClearCacheTime time.Duration `yaml:"crontabClearCacheTime"`
MaxPostIdCacheTime time.Duration `yaml:"maxPostIdCacheTime"`
UserInfoCacheTime time.Duration `yaml:"userInfoCacheTime"`
CommentsCacheTime time.Duration `yaml:"commentsCacheTime"`
ThemeHeaderImagCacheTime time.Duration `yaml:"themeHeaderImagCacheTime"`
SleepTime []time.Duration `yaml:"sleepTime"`
}
type Ssl struct { type Ssl struct {
Cert string `yaml:"cert"` Cert string `yaml:"cert"`
Key string `yaml:"key"` Key string `yaml:"key"`
@ -77,7 +86,7 @@ func InitConfig(conf string) error {
if err != nil { if err != nil {
return err return err
} }
Conf.Store(c) config.Store(c)
return nil return nil
} }

View File

@ -2,22 +2,26 @@ package dao
import ( import (
"context" "context"
"github.com/fthvgb1/wp-go/helper/number"
"github.com/fthvgb1/wp-go/helper/slice" "github.com/fthvgb1/wp-go/helper/slice"
"github.com/fthvgb1/wp-go/internal/pkg/models" "github.com/fthvgb1/wp-go/internal/pkg/models"
"github.com/fthvgb1/wp-go/model" "github.com/fthvgb1/wp-go/model"
"strconv"
) )
// RecentComments // RecentComments
// param context.Context // param context.Context
func RecentComments(a ...any) (r []models.Comments, err error) { func RecentComments(a ...any) (r []models.Comments, err error) {
ctx := a[0].(context.Context) ctx := a[0].(context.Context)
return model.Find[models.Comments](ctx, model.SqlBuilder{ return model.Finds[models.Comments](ctx, model.Conditions(
model.Where(model.SqlBuilder{
{"comment_approved", "1"}, {"comment_approved", "1"},
{"post_status", "publish"}, {"post_status", "publish"},
}, "comment_ID,comment_author,comment_post_ID,post_title", "", model.SqlBuilder{{"comment_date_gmt", "desc"}}, model.SqlBuilder{ }),
{"a", "left join", "wp_posts b", "a.comment_post_ID=b.ID"}, model.Fields("comment_ID,comment_author,comment_post_ID,post_title"),
}, nil, 10) model.Order(model.SqlBuilder{{"comment_date_gmt", "desc"}}),
model.Join(model.SqlBuilder{{"a", "left join", "wp_posts b", "a.comment_post_ID=b.ID"}}),
model.Limit(10),
))
} }
// PostComments // PostComments
@ -26,13 +30,17 @@ func RecentComments(a ...any) (r []models.Comments, err error) {
func PostComments(args ...any) ([]uint64, error) { func PostComments(args ...any) ([]uint64, error) {
ctx := args[0].(context.Context) ctx := args[0].(context.Context)
postId := args[1].(uint64) postId := args[1].(uint64)
r, err := model.Find[models.Comments](ctx, model.SqlBuilder{ r, err := model.Finds[models.Comments](ctx, model.Conditions(
model.Where(model.SqlBuilder{
{"comment_approved", "1"}, {"comment_approved", "1"},
{"comment_post_ID", "=", strconv.FormatUint(postId, 10), "int"}, {"comment_post_ID", "=", number.ToString(postId), "int"},
}, "comment_ID", "", model.SqlBuilder{ }),
model.Fields("comment_ID"),
model.Order(model.SqlBuilder{
{"comment_date_gmt", "asc"}, {"comment_date_gmt", "asc"},
{"comment_ID", "asc"}, {"comment_ID", "asc"},
}, nil, nil, 0) })),
)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -2,7 +2,6 @@ package dao
import ( import (
"context" "context"
"fmt"
"github.com/fthvgb1/wp-go/internal/pkg/models" "github.com/fthvgb1/wp-go/internal/pkg/models"
"github.com/fthvgb1/wp-go/internal/wpconfig" "github.com/fthvgb1/wp-go/internal/wpconfig"
"github.com/fthvgb1/wp-go/model" "github.com/fthvgb1/wp-go/model"
@ -20,23 +19,21 @@ type PostContext struct {
Next models.Posts Next models.Posts
} }
func PasswordProjectTitle(post *models.Posts) {
if post.PostPassword != "" {
post.PostTitle = fmt.Sprintf("密码保护:%s", post.PostTitle)
}
}
func CategoriesAndTags(a ...any) (terms []models.TermsMy, err error) { func CategoriesAndTags(a ...any) (terms []models.TermsMy, err error) {
ctx := a[0].(context.Context) ctx := a[0].(context.Context)
var in = []any{"category", "post_tag"} var in = []any{"category", "post_tag"}
terms, err = model.Find[models.TermsMy](ctx, model.SqlBuilder{ terms, err = model.Finds[models.TermsMy](ctx, model.Conditions(
model.Where(model.SqlBuilder{
{"tt.count", ">", "0", "int"}, {"tt.count", ">", "0", "int"},
{"tt.taxonomy", "in", ""}, {"tt.taxonomy", "in", ""},
}, "t.term_id", "", model.SqlBuilder{ }),
{"t.name", "asc"}, model.Fields("t.term_id"),
}, model.SqlBuilder{ model.Order(model.SqlBuilder{{"t.name", "asc"}}),
model.Join(model.SqlBuilder{
{"t", "inner join", "wp_term_taxonomy tt", "t.term_id = tt.term_id"}, {"t", "inner join", "wp_term_taxonomy tt", "t.term_id = tt.term_id"},
}, nil, 0, in) }),
model.In(in),
))
for i := 0; i < len(terms); i++ { for i := 0; i < len(terms); i++ {
if v, ok := wpconfig.Terms.Load(terms[i].Terms.TermId); ok { if v, ok := wpconfig.Terms.Load(terms[i].Terms.TermId); ok {
terms[i].Terms = v terms[i].Terms = v
@ -49,7 +46,13 @@ func CategoriesAndTags(a ...any) (terms []models.TermsMy, err error) {
} }
func Archives(ctx context.Context) ([]models.PostArchive, error) { func Archives(ctx context.Context) ([]models.PostArchive, error) {
return model.Find[models.PostArchive](ctx, model.SqlBuilder{ return model.Finds[models.PostArchive](ctx, model.Conditions(
{"post_type", "post"}, {"post_status", "publish"}, model.Where(model.SqlBuilder{
}, "YEAR(post_date) AS `year`, MONTH(post_date) AS `month`, count(ID) as posts", "year,month", model.SqlBuilder{{"year", "desc"}, {"month", "desc"}}, nil, nil, 0) {"post_type", "post"},
{"post_status", "publish"},
}),
model.Fields("YEAR(post_date) AS `year`, MONTH(post_date) AS `month`, count(ID) as posts"),
model.Group("year,month"),
model.Order(model.SqlBuilder{{"year", "desc"}, {"month", "desc"}}),
))
} }

View File

@ -14,9 +14,10 @@ func GetPostMetaByPostIds(args ...any) (r map[uint64]map[string]any, err error)
r = make(map[uint64]map[string]any) r = make(map[uint64]map[string]any)
ctx := args[0].(context.Context) ctx := args[0].(context.Context)
ids := args[1].([]uint64) ids := args[1].([]uint64)
rr, err := model.Find[models.PostMeta](ctx, model.SqlBuilder{ rr, err := model.Finds[models.PostMeta](ctx, model.Conditions(
{"post_id", "in", ""}, model.Where(model.SqlBuilder{{"post_id", "in", ""}}),
}, "*", "", nil, nil, nil, 0, slice.ToAnySlice(ids)) model.In(slice.ToAnySlice(ids)),
))
if err != nil { if err != nil {
return return
} }
@ -24,6 +25,7 @@ func GetPostMetaByPostIds(args ...any) (r map[uint64]map[string]any, err error)
if _, ok := r[postmeta.PostId]; !ok { if _, ok := r[postmeta.PostId]; !ok {
r[postmeta.PostId] = make(map[string]any) r[postmeta.PostId] = make(map[string]any)
} }
r[postmeta.PostId][postmeta.MetaKey] = postmeta.MetaValue
if postmeta.MetaKey == "_wp_attachment_metadata" { if postmeta.MetaKey == "_wp_attachment_metadata" {
metadata, err := plugins.UnPHPSerialize[models.WpAttachmentMetadata](postmeta.MetaValue) metadata, err := plugins.UnPHPSerialize[models.WpAttachmentMetadata](postmeta.MetaValue)
if err != nil { if err != nil {
@ -31,11 +33,7 @@ func GetPostMetaByPostIds(args ...any) (r map[uint64]map[string]any, err error)
continue continue
} }
r[postmeta.PostId][postmeta.MetaKey] = metadata r[postmeta.PostId][postmeta.MetaKey] = metadata
} else {
r[postmeta.PostId][postmeta.MetaKey] = postmeta.MetaValue
} }
} }
return return
} }

View File

@ -13,20 +13,21 @@ import (
"time" "time"
) )
func GetPostsByIds(ids ...any) (m map[uint64]models.Posts, err error) { func GetPostsByIds(a ...any) (m map[uint64]models.Posts, err error) {
ctx := ids[0].(context.Context) ctx := a[0].(context.Context)
m = make(map[uint64]models.Posts) m = make(map[uint64]models.Posts)
id := ids[1].([]uint64) ids := a[1].([]uint64)
arg := slice.ToAnySlice(id) rawPosts, err := model.Finds[models.Posts](ctx, model.Conditions(
rawPosts, err := model.Find[models.Posts](ctx, model.SqlBuilder{{ model.Where(model.SqlBuilder{{"Id", "in", ""}}),
"Id", "in", "", model.Join(model.SqlBuilder{
}}, "a.*,ifnull(d.name,'') category_name,ifnull(taxonomy,'') `taxonomy`", "", nil, model.SqlBuilder{{ {"a", "left join", "wp_term_relationships b", "a.Id=b.object_id"},
"a", "left join", "wp_term_relationships b", "a.Id=b.object_id", {"left join", "wp_term_taxonomy c", "b.term_taxonomy_id=c.term_taxonomy_id"},
}, { {"left join", "wp_terms d", "c.term_id=d.term_id"},
"left join", "wp_term_taxonomy c", "b.term_taxonomy_id=c.term_taxonomy_id", }),
}, { model.Fields("a.*,ifnull(d.name,'') category_name,ifnull(taxonomy,'') `taxonomy`"),
"left join", "wp_terms d", "c.term_id=d.term_id", model.In(slice.ToAnySlice(ids)),
}}, nil, 0, arg) ))
if err != nil { if err != nil {
return m, err return m, err
} }
@ -45,7 +46,7 @@ func GetPostsByIds(ids ...any) (m map[uint64]models.Posts, err error) {
} }
//host, _ := wpconfig.Options.Load("siteurl") //host, _ := wpconfig.Options.Load("siteurl")
host := "" host := ""
meta, _ := GetPostMetaByPostIds(ctx, id) meta, _ := GetPostMetaByPostIds(ctx, ids)
for k, pp := range postsMap { for k, pp := range postsMap {
if len(pp.Categories) > 0 { if len(pp.Categories) > 0 {
t := make([]string, 0, len(pp.Categories)) t := make([]string, 0, len(pp.Categories))
@ -97,7 +98,11 @@ func SearchPostIds(args ...any) (ids PostIds, err error) {
join := args[5].(model.SqlBuilder) join := args[5].(model.SqlBuilder)
postType := args[6].([]any) postType := args[6].([]any)
postStatus := args[7].([]any) postStatus := args[7].([]any)
res, total, err := model.SimplePagination[models.Posts](ctx, where, "ID", "", page, limit, order, join, nil, postType, postStatus) res, total, err := model.SimplePagination[models.Posts](
ctx, where, "ID",
"", page, limit, order,
join, nil, postType, postStatus,
)
for _, posts := range res { for _, posts := range res {
ids.Ids = append(ids.Ids, posts.Id) ids.Ids = append(ids.Ids, posts.Id)
} }
@ -112,7 +117,10 @@ func SearchPostIds(args ...any) (ids PostIds, err error) {
func GetMaxPostId(a ...any) (uint64, error) { func GetMaxPostId(a ...any) (uint64, error) {
ctx := a[0].(context.Context) ctx := a[0].(context.Context)
r, err := model.SimpleFind[models.Posts](ctx, model.SqlBuilder{{"post_type", "post"}, {"post_status", "publish"}}, "max(ID) ID") r, err := model.SimpleFind[models.Posts](ctx,
model.SqlBuilder{{"post_type", "post"}, {"post_status", "publish"}},
"max(ID) ID",
)
var id uint64 var id uint64
if len(r) > 0 { if len(r) > 0 {
id = r[0].Id id = r[0].Id
@ -122,14 +130,16 @@ func GetMaxPostId(a ...any) (uint64, error) {
func RecentPosts(a ...any) (r []models.Posts, err error) { func RecentPosts(a ...any) (r []models.Posts, err error) {
ctx := a[0].(context.Context) ctx := a[0].(context.Context)
r, err = model.Find[models.Posts](ctx, model.SqlBuilder{{ num := a[1].(int)
"post_type", "post", r, err = model.Finds[models.Posts](ctx, model.Conditions(
}, {"post_status", "publish"}}, "ID,post_title,post_password", "", model.SqlBuilder{{"post_date", "desc"}}, nil, nil, 10) model.Where(model.SqlBuilder{
for i, post := range r { {"post_type", "post"},
if post.PostPassword != "" { {"post_status", "publish"},
PasswordProjectTitle(&r[i]) }),
} model.Fields("ID,post_title,post_password"),
} model.Order(model.SqlBuilder{{"post_date", "desc"}}),
model.Limit(num),
))
return return
} }
@ -169,19 +179,15 @@ func MonthPost(args ...any) (r []uint64, err error) {
ctx := args[0].(context.Context) ctx := args[0].(context.Context)
year, month := args[1].(string), args[2].(string) year, month := args[1].(string), args[2].(string)
where := model.SqlBuilder{ where := model.SqlBuilder{
{"post_type", "in", ""}, {"post_type", "post"},
{"post_status", "in", ""}, {"post_status", "publish"},
{"year(post_date)", year}, {"year(post_date)", year},
{"month(post_date)", month}, {"month(post_date)", month},
} }
postType := []any{"post"} return model.Column[models.Posts, uint64](ctx, func(v models.Posts) (uint64, bool) {
status := []any{"publish"} return v.Id, true
ids, err := model.Find[models.Posts](ctx, where, "ID", "", model.SqlBuilder{{"Id", "asc"}}, nil, nil, 0, postType, status) }, model.Conditions(
if err != nil { model.Fields("ID"),
return model.Where(where),
} ))
for _, post := range ids {
r = append(r, post.Id)
}
return
} }

View File

@ -1,78 +1,32 @@
package db package db
import ( import (
"context"
"fmt"
"github.com/fthvgb1/wp-go/internal/pkg/config" "github.com/fthvgb1/wp-go/internal/pkg/config"
_ "github.com/go-sql-driver/mysql" _ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
"log"
"os"
"strconv"
"strings"
) )
var Db *sqlx.DB var db *sqlx.DB
type SqlxDb struct { func InitDb() (*sqlx.DB, error) {
sqlx *sqlx.DB c := config.GetConfig()
}
func NewSqlxDb(sqlx *sqlx.DB) *SqlxDb {
return &SqlxDb{sqlx: sqlx}
}
func (r SqlxDb) Select(ctx context.Context, dest any, sql string, params ...any) error {
if os.Getenv("SHOW_SQL") == "true" {
go log.Println(formatSql(sql, params))
}
return r.sqlx.Select(dest, sql, params...)
}
func (r SqlxDb) Get(ctx context.Context, dest any, sql string, params ...any) error {
if os.Getenv("SHOW_SQL") == "true" {
go log.Println(formatSql(sql, params))
}
return r.sqlx.Get(dest, sql, params...)
}
func formatSql(sql string, params []any) string {
for _, param := range params {
switch param.(type) {
case string:
sql = strings.Replace(sql, "?", fmt.Sprintf("'%s'", param.(string)), 1)
case int64:
sql = strings.Replace(sql, "?", strconv.FormatInt(param.(int64), 10), 1)
case int:
sql = strings.Replace(sql, "?", strconv.Itoa(param.(int)), 1)
case uint64:
sql = strings.Replace(sql, "?", strconv.FormatUint(param.(uint64), 10), 1)
case float64:
sql = strings.Replace(sql, "?", fmt.Sprintf("%f", param.(float64)), 1)
}
}
return sql
}
func InitDb() error {
c := config.Conf.Load()
dsn := c.Mysql.Dsn.GetDsn() dsn := c.Mysql.Dsn.GetDsn()
var err error var err error
Db, err = sqlx.Open("mysql", dsn) db, err = sqlx.Open("mysql", dsn)
if err != nil { if err != nil {
return err return nil, err
} }
if c.Mysql.Pool.ConnMaxIdleTime != 0 { if c.Mysql.Pool.ConnMaxIdleTime != 0 {
Db.SetConnMaxIdleTime(c.Mysql.Pool.ConnMaxLifetime) db.SetConnMaxIdleTime(c.Mysql.Pool.ConnMaxLifetime)
} }
if c.Mysql.Pool.MaxIdleConn != 0 { if c.Mysql.Pool.MaxIdleConn != 0 {
Db.SetMaxIdleConns(c.Mysql.Pool.MaxIdleConn) db.SetMaxIdleConns(c.Mysql.Pool.MaxIdleConn)
} }
if c.Mysql.Pool.MaxOpenConn != 0 { if c.Mysql.Pool.MaxOpenConn != 0 {
Db.SetMaxOpenConns(c.Mysql.Pool.MaxOpenConn) db.SetMaxOpenConns(c.Mysql.Pool.MaxOpenConn)
} }
if c.Mysql.Pool.ConnMaxLifetime != 0 { if c.Mysql.Pool.ConnMaxLifetime != 0 {
Db.SetConnMaxLifetime(c.Mysql.Pool.ConnMaxLifetime) db.SetConnMaxLifetime(c.Mysql.Pool.ConnMaxLifetime)
} }
return err return db, err
} }

View File

@ -3,6 +3,7 @@ package models
import "time" import "time"
type Posts struct { type Posts struct {
post
Id uint64 `gorm:"column:ID" db:"ID" json:"ID" form:"ID"` Id uint64 `gorm:"column:ID" db:"ID" json:"ID" form:"ID"`
PostAuthor uint64 `gorm:"column:post_author" db:"post_author" json:"post_author" form:"post_author"` PostAuthor uint64 `gorm:"column:post_author" db:"post_author" json:"post_author" form:"post_author"`
PostDate time.Time `gorm:"column:post_date" db:"post_date" json:"post_date" form:"post_date"` PostDate time.Time `gorm:"column:post_date" db:"post_date" json:"post_date" form:"post_date"`
@ -47,23 +48,19 @@ type PostThumbnail struct {
OriginAttachmentData WpAttachmentMetadata OriginAttachmentData WpAttachmentMetadata
} }
func (w Posts) PrimaryKey() string { type post struct {
}
func (w post) PrimaryKey() string {
return "ID" return "ID"
} }
func (w Posts) Table() string { func (w post) Table() string {
return "wp_posts"
}
func (w PostArchive) PrimaryKey() string {
return "ID"
}
func (w PostArchive) Table() string {
return "wp_posts" return "wp_posts"
} }
type PostArchive struct { type PostArchive struct {
post
Year string `db:"year"` Year string `db:"year"`
Month string `db:"month"` Month string `db:"month"`
Posts int `db:"posts"` Posts int `db:"posts"`

View File

@ -17,7 +17,7 @@ var ctx context.Context
func InitDigestCache() { func InitDigestCache() {
ctx = context.Background() ctx = context.Background()
digestCache = cache.NewMemoryMapCacheByFn[uint64](digestRaw, config.Conf.Load().DigestCacheTime) digestCache = cache.NewMemoryMapCacheByFn[uint64](digestRaw, config.GetConfig().CacheTime.DigestCacheTime)
} }
func ClearDigestCache() { func ClearDigestCache() {
@ -30,7 +30,7 @@ func FlushCache() {
func digestRaw(arg ...any) (string, error) { func digestRaw(arg ...any) (string, error) {
str := arg[0].(string) str := arg[0].(string)
id := arg[1].(uint64) id := arg[1].(uint64)
limit := config.Conf.Load().DigestWordCount limit := config.GetConfig().DigestWordCount
if limit < 0 { if limit < 0 {
return str, nil return str, nil
} else if limit == 0 { } else if limit == 0 {

View File

@ -19,18 +19,14 @@ func ApplyPlugin(p *Plugin[models.Posts], post *models.Posts) {
} }
func PasswordProjectTitle(post *models.Posts) { func PasswordProjectTitle(post *models.Posts) {
if post.PostPassword != "" {
post.PostTitle = fmt.Sprintf("密码保护:%s", post.PostTitle) post.PostTitle = fmt.Sprintf("密码保护:%s", post.PostTitle)
}
} }
func PasswdProjectContent(post *models.Posts) { func PasswdProjectContent(post *models.Posts) {
if post.PostContent != "" {
format := ` format := `
<form action="/login" class="post-password-form" method="post"> <form action="/login" class="post-password-form" method="post">
<p>此内容受密码保护如需查阅请在下列字段中输入您的密码</p> <p>此内容受密码保护如需查阅请在下列字段中输入您的密码</p>
<p><label for="pwbox-%d">密码 <input name="post_password" id="pwbox-%d" type="password" size="20"></label> <input type="submit" name="Submit" value="提交"></p> <p><label for="pwbox-%d">密码 <input name="post_password" id="pwbox-%d" type="password" size="20"></label> <input type="submit" name="Submit" value="提交"></p>
</form>` </form>`
post.PostContent = fmt.Sprintf(format, post.Id, post.Id) post.PostContent = fmt.Sprintf(format, post.Id, post.Id)
}
} }

View File

@ -11,7 +11,7 @@ func InitThemeAndTemplateFuncMap() {
} }
func GetTemplateName() string { func GetTemplateName() string {
tmlp := config.Conf.Load().Theme tmlp := config.GetConfig().Theme
if tmlp == "" { if tmlp == "" {
tmlp = wpconfig.Options.Value("template") tmlp = wpconfig.Options.Value("template")
} }

View File

@ -139,23 +139,16 @@ func (h handle) bodyClass() string {
s := "" s := ""
switch h.scene { switch h.scene {
case plugins.Search: case plugins.Search:
s = "search-no-results"
if len(h.ginH["posts"].([]models.Posts)) > 0 { if len(h.ginH["posts"].([]models.Posts)) > 0 {
s = "search-results" s = "search-results"
} else {
s = "search-no-results"
} }
case plugins.Category: case plugins.Category, plugins.Tag:
cat := h.c.Param("category") cat := h.c.Param("category")
_, cate := slice.SearchFirst(cache.Categories(h.c), func(my models.TermsMy) bool { if cat == "" {
return my.Name == cat cat = h.c.Param("tag")
})
if cate.Slug[0] != '%' {
s = cate.Slug
} }
s = fmt.Sprintf("category-%d %v", cate.Terms.TermId, s) _, cate := slice.SearchFirst(cache.CategoriesTags(h.c, h.scene), func(my models.TermsMy) bool {
case plugins.Tag:
cat := h.c.Param("tag")
_, cate := slice.SearchFirst(cache.Tags(h.c), func(my models.TermsMy) bool {
return my.Name == cat return my.Name == cat
}) })
if cate.Slug[0] != '%' { if cate.Slug[0] != '%' {

View File

@ -8,7 +8,7 @@ import (
"strings" "strings"
) )
func SimplePagination[T Model](ctx context.Context, where ParseWhere, fields, group string, page, pageSize int, order SqlBuilder, join SqlBuilder, having SqlBuilder, in ...[]any) (r []T, total int, err error) { func pagination[T Model](db dbQuery, ctx context.Context, where ParseWhere, fields, group string, page, pageSize int, order SqlBuilder, join SqlBuilder, having SqlBuilder, in ...[]any) (r []T, total int, err error) {
var rr T var rr T
var w string var w string
var args []any var args []any
@ -55,11 +55,11 @@ func SimplePagination[T Model](ctx context.Context, where ParseWhere, fields, gr
if group == "" { if group == "" {
tpx := "select count(*) n from %s %s %s limit 1" tpx := "select count(*) n from %s %s %s limit 1"
sq := fmt.Sprintf(tpx, rr.Table(), j, w) sq := fmt.Sprintf(tpx, rr.Table(), j, w)
err = globalBb.Get(ctx, &n, sq, args...) err = db.Get(ctx, &n, sq, args...)
} else { } else {
tpx := "select count(*) n from (select %s from %s %s %s %s %s ) %s" tpx := "select count(*) n from (select %s from %s %s %s %s %s ) %s"
sq := fmt.Sprintf(tpx, group, rr.Table(), j, w, groupBy, h, fmt.Sprintf("table%d", rand.Int())) sq := fmt.Sprintf(tpx, group, rr.Table(), j, w, groupBy, h, fmt.Sprintf("table%d", rand.Int()))
err = globalBb.Get(ctx, &n, sq, args...) err = db.Get(ctx, &n, sq, args...)
} }
if err != nil { if err != nil {
@ -78,13 +78,18 @@ func SimplePagination[T Model](ctx context.Context, where ParseWhere, fields, gr
} }
tp := "select %s from %s %s %s %s %s %s limit %d,%d" tp := "select %s from %s %s %s %s %s %s limit %d,%d"
sq := fmt.Sprintf(tp, fields, rr.Table(), j, w, groupBy, h, order.parseOrderBy(), offset, pageSize) sq := fmt.Sprintf(tp, fields, rr.Table(), j, w, groupBy, h, order.parseOrderBy(), offset, pageSize)
err = globalBb.Select(ctx, &r, sq, args...) err = db.Select(ctx, &r, sq, args...)
if err != nil { if err != nil {
return return
} }
return return
} }
func SimplePagination[T Model](ctx context.Context, where ParseWhere, fields, group string, page, pageSize int, order SqlBuilder, join SqlBuilder, having SqlBuilder, in ...[]any) (r []T, total int, err error) {
r, total, err = pagination[T](globalBb, ctx, where, fields, group, page, pageSize, order, join, having, in...)
return
}
func FindOneById[T Model, I constraints.Integer](ctx context.Context, id I) (T, error) { func FindOneById[T Model, I constraints.Integer](ctx context.Context, id I) (T, error) {
var r T var r T
sq := fmt.Sprintf("select * from `%s` where `%s`=?", r.Table(), r.PrimaryKey()) sq := fmt.Sprintf("select * from `%s` where `%s`=?", r.Table(), r.PrimaryKey())

View File

@ -3,15 +3,11 @@ package model
import ( import (
"context" "context"
"database/sql" "database/sql"
"fmt"
"github.com/fthvgb1/wp-go/helper/number" "github.com/fthvgb1/wp-go/helper/number"
"github.com/fthvgb1/wp-go/helper/slice" "github.com/fthvgb1/wp-go/helper/slice"
_ "github.com/go-sql-driver/mysql" _ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
"log"
"reflect" "reflect"
"strconv"
"strings"
"testing" "testing"
"time" "time"
) )
@ -102,40 +98,6 @@ func (p post) Table() string {
return "wp_posts" return "wp_posts"
} }
type SqlxDb struct {
sqlx *sqlx.DB
}
var Db *SqlxDb
func (r SqlxDb) Select(_ context.Context, dest any, sql string, params ...any) error {
log.Println(formatSql(sql, params))
return r.sqlx.Select(dest, sql, params...)
}
func (r SqlxDb) Get(_ context.Context, dest any, sql string, params ...any) error {
log.Println(formatSql(sql, params))
return r.sqlx.Get(dest, sql, params...)
}
func formatSql(sql string, params []any) string {
for _, param := range params {
switch param.(type) {
case string:
sql = strings.Replace(sql, "?", fmt.Sprintf("'%s'", param.(string)), 1)
case int64:
sql = strings.Replace(sql, "?", strconv.FormatInt(param.(int64), 10), 1)
case int:
sql = strings.Replace(sql, "?", strconv.Itoa(param.(int)), 1)
case uint64:
sql = strings.Replace(sql, "?", strconv.FormatUint(param.(uint64), 10), 1)
case float64:
sql = strings.Replace(sql, "?", fmt.Sprintf("%f", param.(float64)), 1)
}
}
return sql
}
var ctx = context.Background() var ctx = context.Background()
func init() { func init() {
@ -143,8 +105,7 @@ func init() {
if err != nil { if err != nil {
panic(err) panic(err)
} }
Db = &SqlxDb{db} InitDB(NewSqlxQuery(db))
InitDB(Db)
} }
func TestFind(t *testing.T) { func TestFind(t *testing.T) {
type args struct { type args struct {

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"database/sql" "database/sql"
"fmt" "fmt"
"github.com/fthvgb1/wp-go/helper/slice"
"strings" "strings"
) )
@ -11,6 +12,19 @@ import (
// //
// Conditions 中可用 Where Fields Group Having Join Order Offset Limit In 函数 // Conditions 中可用 Where Fields Group Having Join Order Offset Limit In 函数
func Finds[T Model](ctx context.Context, q *QueryCondition) (r []T, err error) { func Finds[T Model](ctx context.Context, q *QueryCondition) (r []T, err error) {
r, err = finds[T](globalBb, ctx, q)
return
}
// DBFind 同 Finds 使用指定 db 查询
//
// Conditions 中可用 Where Fields Group Having Join Order Offset Limit In 函数
func DBFind[T Model](db dbQuery, ctx context.Context, q *QueryCondition) (r []T, err error) {
r, err = finds[T](db, ctx, q)
return
}
func finds[T Model](db dbQuery, ctx context.Context, q *QueryCondition) (r []T, err error) {
var rr T var rr T
w := "" w := ""
var args []any var args []any
@ -48,25 +62,22 @@ func Finds[T Model](ctx context.Context, q *QueryCondition) (r []T, err error) {
l = fmt.Sprintf(" %s offset %d", l, q.offset) l = fmt.Sprintf(" %s offset %d", l, q.offset)
} }
sq := fmt.Sprintf(tp, q.fields, rr.Table(), j, w, groupBy, h, q.order.parseOrderBy(), l) sq := fmt.Sprintf(tp, q.fields, rr.Table(), j, w, groupBy, h, q.order.parseOrderBy(), l)
err = globalBb.Select(ctx, &r, sq, args...) err = db.Select(ctx, &r, sq, args...)
return return
} }
// ChunkFind 分片查询并直接返回所有结果 func chunkFind[T Model](db dbQuery, ctx context.Context, perLimit int, q *QueryCondition) (r []T, err error) {
//
// Conditions 中可用 Where Fields Group Having Join Order Limit In 函数
func ChunkFind[T Model](ctx context.Context, perLimit int, q *QueryCondition) (r []T, err error) {
i := 1 i := 1
var rr []T var rr []T
var total int var total int
var offset int var offset int
for { for {
if 1 == i { if 1 == i {
rr, total, err = SimplePagination[T](ctx, q.where, q.fields, q.group, i, perLimit, q.order, q.join, q.having, q.in...) rr, total, err = pagination[T](db, ctx, q.where, q.fields, q.group, i, perLimit, q.order, q.join, q.having, q.in...)
} else { } else {
q.offset = offset q.offset = offset
q.limit = perLimit q.limit = perLimit
rr, err = Finds[T](ctx, q) rr, err = finds[T](db, ctx, q)
} }
offset += perLimit offset += perLimit
if (err != nil && err != sql.ErrNoRows) || len(rr) < 1 { if (err != nil && err != sql.ErrNoRows) || len(rr) < 1 {
@ -81,10 +92,39 @@ func ChunkFind[T Model](ctx context.Context, perLimit int, q *QueryCondition) (r
return return
} }
// ChunkFind 分片查询并直接返回所有结果
//
// Conditions 中可用 Where Fields Group Having Join Order Limit In 函数
func ChunkFind[T Model](ctx context.Context, perLimit int, q *QueryCondition) (r []T, err error) {
r, err = chunkFind[T](globalBb, ctx, perLimit, q)
return
}
// DBChunkFind 同 ChunkFind
//
// Conditions 中可用 Where Fields Group Having Join Order Limit In 函数
func DBChunkFind[T Model](db dbQuery, ctx context.Context, perLimit int, q *QueryCondition) (r []T, err error) {
r, err = chunkFind[T](db, ctx, perLimit, q)
return
}
// Chunk 分片查询并函数过虑返回新类型的切片 // Chunk 分片查询并函数过虑返回新类型的切片
// //
// Conditions 中可用 Where Fields Group Having Join Order Limit In 函数 // Conditions 中可用 Where Fields Group Having Join Order Limit In 函数
func Chunk[T Model, R any](ctx context.Context, perLimit int, fn func(rows T) (R, bool), q *QueryCondition) (r []R, err error) { func Chunk[T Model, R any](ctx context.Context, perLimit int, fn func(rows T) (R, bool), q *QueryCondition) (r []R, err error) {
r, err = chunk(globalBb, ctx, perLimit, fn, q)
return
}
// DBChunk 同 Chunk
//
// Conditions 中可用 Where Fields Group Having Join Order Limit In 函数
func DBChunk[T Model, R any](db dbQuery, ctx context.Context, perLimit int, fn func(rows T) (R, bool), q *QueryCondition) (r []R, err error) {
r, err = chunk(db, ctx, perLimit, fn, q)
return
}
func chunk[T Model, R any](db dbQuery, ctx context.Context, perLimit int, fn func(rows T) (R, bool), q *QueryCondition) (r []R, err error) {
i := 1 i := 1
var rr []T var rr []T
var count int var count int
@ -92,11 +132,11 @@ func Chunk[T Model, R any](ctx context.Context, perLimit int, fn func(rows T) (R
var offset int var offset int
for { for {
if 1 == i { if 1 == i {
rr, total, err = SimplePagination[T](ctx, q.where, q.fields, q.group, i, perLimit, q.order, q.join, q.having, q.in...) rr, total, err = pagination[T](db, ctx, q.where, q.fields, q.group, i, perLimit, q.order, q.join, q.having, q.in...)
} else { } else {
q.offset = offset q.offset = offset
q.limit = perLimit q.limit = perLimit
rr, err = Finds[T](ctx, q) rr, err = finds[T](db, ctx, q)
} }
offset += perLimit offset += perLimit
if (err != nil && err != sql.ErrNoRows) || len(rr) < 1 { if (err != nil && err != sql.ErrNoRows) || len(rr) < 1 {
@ -123,3 +163,26 @@ func Chunk[T Model, R any](ctx context.Context, perLimit int, fn func(rows T) (R
func Pagination[T Model](ctx context.Context, q *QueryCondition) ([]T, int, error) { func Pagination[T Model](ctx context.Context, q *QueryCondition) ([]T, int, error) {
return SimplePagination[T](ctx, q.where, q.fields, q.group, q.page, q.limit, q.order, q.join, q.having, q.in...) return SimplePagination[T](ctx, q.where, q.fields, q.group, q.page, q.limit, q.order, q.join, q.having, q.in...)
} }
// DBPagination 同 Pagination 方便多个db使用
//
// Condition 中可使用 Where Fields Group Having Join Order Page Limit In 函数
func DBPagination[T Model](db dbQuery, ctx context.Context, q *QueryCondition) ([]T, int, error) {
return pagination[T](db, ctx, q.where, q.fields, q.group, q.page, q.limit, q.order, q.join, q.having, q.in...)
}
func Column[V Model, T any](ctx context.Context, fn func(V) (T, bool), q *QueryCondition) ([]T, error) {
return column[V, T](globalBb, ctx, fn, q)
}
func DBColumn[V Model, T any](db dbQuery, ctx context.Context, fn func(V) (T, bool), q *QueryCondition) (r []T, err error) {
return column[V, T](db, ctx, fn, q)
}
func column[V Model, T any](db dbQuery, ctx context.Context, fn func(V) (T, bool), q *QueryCondition) (r []T, err error) {
res, err := finds[V](db, ctx, q)
if err != nil {
return nil, err
}
r = slice.FilterAndMap(res, fn)
return
}

View File

@ -230,3 +230,46 @@ func TestPagination(t *testing.T) {
}) })
} }
} }
func TestColumn(t *testing.T) {
type args[V Model, T any] struct {
ctx context.Context
fn func(V) (T, bool)
q *QueryCondition
}
type testCase[V Model, T any] struct {
name string
args args[V, T]
wantR []T
wantErr bool
}
tests := []testCase[post, uint64]{
{
name: "t1",
args: args[post, uint64]{
ctx: ctx,
fn: func(t post) (uint64, bool) {
return t.Id, true
},
q: Conditions(
Where(SqlBuilder{
{"ID", "<", "200", "int"},
}),
),
},
wantR: []uint64{63, 64, 190, 193},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotR, err := Column[post](tt.args.ctx, tt.args.fn, tt.args.q)
if (err != nil) != tt.wantErr {
t.Errorf("Column() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(gotR, tt.wantR) {
t.Errorf("Column() gotR = %v, want %v", gotR, tt.wantR)
}
})
}
}

51
model/sqxquery.go Normal file
View File

@ -0,0 +1,51 @@
package model
import (
"context"
"fmt"
"github.com/jmoiron/sqlx"
"log"
"os"
"strconv"
"strings"
)
type SqlxQuery struct {
sqlx *sqlx.DB
}
func NewSqlxQuery(sqlx *sqlx.DB) SqlxQuery {
return SqlxQuery{sqlx: sqlx}
}
func (r SqlxQuery) Select(ctx context.Context, dest any, sql string, params ...any) error {
if os.Getenv("SHOW_SQL") == "true" {
go log.Println(formatSql(sql, params))
}
return r.sqlx.Select(dest, sql, params...)
}
func (r SqlxQuery) Get(ctx context.Context, dest any, sql string, params ...any) error {
if os.Getenv("SHOW_SQL") == "true" {
go log.Println(formatSql(sql, params))
}
return r.sqlx.Get(dest, sql, params...)
}
func formatSql(sql string, params []any) string {
for _, param := range params {
switch param.(type) {
case string:
sql = strings.Replace(sql, "?", fmt.Sprintf("'%s'", param.(string)), 1)
case int64:
sql = strings.Replace(sql, "?", strconv.FormatInt(param.(int64), 10), 1)
case int:
sql = strings.Replace(sql, "?", strconv.Itoa(param.(int)), 1)
case uint64:
sql = strings.Replace(sql, "?", strconv.FormatUint(param.(uint64), 10), 1)
case float64:
sql = strings.Replace(sql, "?", fmt.Sprintf("%f", param.(float64)), 1)
}
}
return sql
}