From a9a890a84991e717a1873937fdf32f7fe2361801 Mon Sep 17 00:00:00 2001 From: Zhang Chao Date: Wed, 29 Jul 2026 16:45:22 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=9F=BA=E7=A1=80=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- database/database.go | 2 + go.mod | 29 ++++++----- go.sum | 85 ++++++++++++++++++++----------- goblog.db | Bin 208896 -> 290816 bytes handlers/comment.go | 19 ++++++- handlers/setting.go | 84 +++++++++++++++++++++++++++++++ handlers/view.go | 94 +++++++++++++++++++++++++---------- models/models.go | 9 ++-- routers/router.go | 6 ++- static/admin/app.js | 96 ++++++++++++++++++++++++++++++++++-- static/admin/index.html | 34 ++++++++++++- static/admin/style.css | 50 +++++++++++++++++++ templates/default/post.html | 16 +++++- templates/minimal/post.html | 16 +++++- templates/mytheme/post.html | 16 +++++- 15 files changed, 472 insertions(+), 84 deletions(-) create mode 100644 handlers/setting.go diff --git a/database/database.go b/database/database.go index eeffc23..d3bb7d5 100644 --- a/database/database.go +++ b/database/database.go @@ -6,6 +6,7 @@ import ( "log/slog" "github.com/glebarez/sqlite" + "gorm.io/gorm" "gorm.io/gorm/logger" ) @@ -18,6 +19,7 @@ func Init(cfg *config.DatabaseConfig) error { DB, err = gorm.Open(sqlite.Open(cfg.DSN), &gorm.Config{ Logger: logger.Default.LogMode(logger.Info), }) + if err != nil { return err } diff --git a/go.mod b/go.mod index 431a8fb..c92f6a3 100644 --- a/go.mod +++ b/go.mod @@ -10,8 +10,9 @@ require ( github.com/golang-jwt/jwt/v5 v5.2.1 github.com/spf13/viper v1.21.0 github.com/yuin/goldmark v1.8.5 - golang.org/x/crypto v0.48.0 - gorm.io/gorm v1.31.1 + golang.org/x/crypto v0.53.0 + gorm.io/driver/sqlite v1.6.0 + gorm.io/gorm v1.31.2 ) require ( @@ -29,15 +30,17 @@ require ( github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/goccy/go-yaml v1.18.0 // indirect - github.com/google/uuid v1.3.0 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-sqlite3 v1.14.22 // indirect github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/ncruces/go-strftime v1.0.0 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/quic-go/qpack v0.5.1 // indirect github.com/quic-go/quic-go v0.54.0 // indirect @@ -53,15 +56,15 @@ require ( go.uber.org/mock v0.5.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/arch v0.20.0 // indirect - golang.org/x/mod v0.32.0 // indirect - golang.org/x/net v0.49.0 // indirect - golang.org/x/sync v0.19.0 // indirect - golang.org/x/sys v0.41.0 // indirect - golang.org/x/text v0.34.0 // indirect - golang.org/x/tools v0.41.0 // indirect + golang.org/x/mod v0.37.0 // indirect + golang.org/x/net v0.56.0 // indirect + golang.org/x/sync v0.21.0 // indirect + golang.org/x/sys v0.46.0 // indirect + golang.org/x/text v0.39.0 // indirect + golang.org/x/tools v0.47.0 // indirect google.golang.org/protobuf v1.36.9 // indirect - modernc.org/libc v1.22.5 // indirect - modernc.org/mathutil v1.5.0 // indirect - modernc.org/memory v1.5.0 // indirect - modernc.org/sqlite v1.23.1 // indirect + modernc.org/libc v1.74.1 // indirect + modernc.org/mathutil v1.7.1 // indirect + modernc.org/memory v1.11.0 // indirect + modernc.org/sqlite v1.54.0 // indirect ) diff --git a/go.sum b/go.sum index 8993b60..96b350a 100644 --- a/go.sum +++ b/go.sum @@ -46,10 +46,12 @@ github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVI github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= -github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= @@ -66,10 +68,14 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= +github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -78,7 +84,6 @@ github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg= github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= -github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= @@ -118,21 +123,21 @@ go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= -golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= -golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= -golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= -golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= -golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= -golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= -golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= -golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= +golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= +golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= +golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= +golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= +golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= +golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= +golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= -golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= -golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= -golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= -golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= +golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= +golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.39.0 h1:UbZz4pLOvn600D6Oh6GGEI6VAmndrEBLv8/6BEXzyus= +golang.org/x/text v0.39.0/go.mod h1:3UwRclnC2g0TU9x8PZiyfOajCd1zaUNHF9cvqcQZ+ZM= +golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q= +golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA= google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -141,13 +146,35 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg= -gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= -modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE= -modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY= -modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= -modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= -modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds= -modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= -modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM= -modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk= +gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ= +gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8= +gorm.io/gorm v1.31.2 h1:3o8FXNo9v9S858gil+3LlZA1LkCOzgb4g5BL64FgaCo= +gorm.io/gorm v1.31.2/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= +modernc.org/cc/v4 v4.29.0 h1:CXgwL8cvxmyzBQZzbSl/6xFtMCryb6u8IOqDci39cgc= +modernc.org/cc/v4 v4.29.0/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI= +modernc.org/ccgo/v4 v4.34.6 h1:sBgfIwyN0TQ9C5hwIeuqyeAKyMWnbvj2fvpF4L11uzU= +modernc.org/ccgo/v4 v4.34.6/go.mod h1:SZ8YcN9NG7XVsQYdm6jYBvi8PQP1qi+kqB6OhjqI3Fk= +modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM= +modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU= +modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= +modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= +modernc.org/gc/v3 v3.1.4 h1:2g65LGVSmFQrXeITAw97x7hCRvZFcyE1uDP+7Vng7JI= +modernc.org/gc/v3 v3.1.4/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= +modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= +modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= +modernc.org/libc v1.74.1 h1:bdR4VTKFMC4966QSNZ05XLGI/VwzVa2kTUX51Dm0riQ= +modernc.org/libc v1.74.1/go.mod h1:uH4t5bOx3G3g9Xcmj10YKlTcVISlRDwv8VoQJG9n8Os= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= +modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg= +modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= +modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= +modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= +modernc.org/sqlite v1.54.0 h1:JCxR4qwkJvOaqAoYcgDoO25Nc+ROg6EJ2LfBVzdrgog= +modernc.org/sqlite v1.54.0/go.mod h1:4ntCLuNmnH8+GNqjka1wNg7KJd5/Hi5FYp8K+XQ7GZw= +modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= +modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= diff --git a/goblog.db b/goblog.db index d6277efcd02b28ecb18ef899d1f68553cc45bf16..c7701870e0dc2bbc159273f92394b88a7f658397 100644 GIT binary patch delta 6959 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 InYQnL0}r1~R{#J2 delta 3040 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 J!Yg42{{>Z+1aSZW diff --git a/handlers/comment.go b/handlers/comment.go index e61cf61..468ac5f 100644 --- a/handlers/comment.go +++ b/handlers/comment.go @@ -7,6 +7,7 @@ import ( "goblog/models" "github.com/gin-gonic/gin" + "gorm.io/gorm" ) // 创建评论请求 @@ -24,7 +25,10 @@ func GetComments(c *gin.Context) { postID := c.Query("post_id") status := c.Query("status") - db := database.DB.Model(&models.Comment{}).Preload("User") + db := database.DB.Model(&models.Comment{}).Preload("User"). + Preload("Post", func(db *gorm.DB) *gorm.DB { + return db.Select("id", "title", "slug") + }) if postID != "" { db = db.Where("post_id = ?", postID) @@ -36,7 +40,18 @@ func GetComments(c *gin.Context) { var comments []models.Comment db.Order("created_at DESC").Find(&comments) - c.JSON(http.StatusOK, gin.H{"data": comments}) + // Comment.Post 在 JSON 中被隐藏,额外附加文章标题和别名供前端展示 + type commentWithPost struct { + models.Comment + PostTitle string `json:"post_title"` + PostSlug string `json:"post_slug"` + } + list := make([]commentWithPost, 0, len(comments)) + for _, cm := range comments { + list = append(list, commentWithPost{cm, cm.Post.Title, cm.Post.Slug}) + } + + c.JSON(http.StatusOK, gin.H{"data": list}) } // 创建评论 diff --git a/handlers/setting.go b/handlers/setting.go new file mode 100644 index 0000000..6466078 --- /dev/null +++ b/handlers/setting.go @@ -0,0 +1,84 @@ +package handlers + +import ( + "net/http" + "strconv" + + "goblog/config" + "goblog/database" + "goblog/models" + + "github.com/gin-gonic/gin" +) + +// 站点基础设置键名 +const ( + OptionSiteName = "site_name" + OptionSiteDesc = "site_desc" + OptionShowComments = "show_comments" // "1" 显示评论 / "0" 隐藏评论 + OptionCommentPageSize = "comments_per_page" // 前台评论每页条数 +) + +// GetSettings 获取站点基础设置(管理员) +func GetSettings(c *gin.Context) { + cfg := config.GetGlobalConfig() + if cfg == nil { + cfg = config.Load() + } + + opts, err := models.GetOptionsByUser(database.DB, 0) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "读取设置失败"}) + return + } + + // 默认值:站点信息取配置文件,评论默认显示、每页 10 条 + settings := map[string]string{ + OptionSiteName: cfg.App.Name, + OptionSiteDesc: cfg.App.Description, + OptionShowComments: "1", + OptionCommentPageSize: "10", + } + for name := range settings { + if v, ok := opts[name]; ok && v != "" { + settings[name] = v + } + } + + c.JSON(http.StatusOK, gin.H{"data": settings}) +} + +// UpdateSettingsRequest 更新站点基础设置请求 +type UpdateSettingsRequest struct { + SiteName string `json:"site_name" binding:"required"` + SiteDesc string `json:"site_desc"` + ShowComments bool `json:"show_comments"` + CommentsPerPage int `json:"comments_per_page" binding:"required,min=1,max=100"` +} + +// UpdateSettings 保存站点基础设置(管理员) +func UpdateSettings(c *gin.Context) { + var req UpdateSettingsRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误: " + err.Error()}) + return + } + + showComments := "0" + if req.ShowComments { + showComments = "1" + } + options := map[string]string{ + OptionSiteName: req.SiteName, + OptionSiteDesc: req.SiteDesc, + OptionShowComments: showComments, + OptionCommentPageSize: strconv.Itoa(req.CommentsPerPage), + } + + if err := models.BatchSetOptions(database.DB, 0, options); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "保存设置失败"}) + return + } + + c.JSON(http.StatusOK, gin.H{"message": "设置已保存"}) +} diff --git a/handlers/view.go b/handlers/view.go index 6e8c1c1..84f1ed8 100644 --- a/handlers/view.go +++ b/handlers/view.go @@ -122,12 +122,11 @@ func IndexView(c *gin.Context) { siteName := cfg.App.Name siteDesc := cfg.App.Description - var option models.Option - if err := database.DB.Where("name = ? AND user_id = ?", "site_name", 0).First(&option).Error; err == nil { - siteName = option.Value + if v, err := models.GetOptionValue(database.DB, "site_name", 0); err == nil { + siteName = v } - if err := database.DB.Where("name = ? AND user_id = ?", "site_desc", 0).First(&option).Error; err == nil { - siteDesc = option.Value + if v, err := models.GetOptionValue(database.DB, "site_desc", 0); err == nil { + siteDesc = v } data := IndexData{ @@ -164,6 +163,10 @@ type PostDetailData struct { Year int PrevPost *models.Post // 上一篇 NextPost *models.Post // 下一篇 + // 评论展示设置与分页 + ShowComments bool + CommentPage int + CommentTotalPages int // Sidebar 数据 Categories []models.Category Tags []models.Tag @@ -180,14 +183,17 @@ func PostView(c *gin.Context) { slug := c.Param("slug") var post models.Post - err := database.DB.Preload("Category").Preload("Tags").Preload("Author"). - Preload("Comments", func(db *gorm.DB) *gorm.DB { - return db.Where("status = ? AND parent_id IS NULL", "approved").Preload("Children", func(db *gorm.DB) *gorm.DB { - return db.Where("status = ?", "approved") - }) - }). - Where("slug = ? AND status = ?", slug, "published"). - First(&post).Error + query := database.DB.Preload("Category").Preload("Tags").Preload("Author"). + Where("status = ?", "published"). + Session(&gorm.Session{}) // 创建可安全复用的查询会话,避免两次查询条件叠加 + + err := query.Where("slug = ?", slug).First(&post).Error + // slug 未命中时,若参数为纯数字则回退按文章 ID 查询(兼容 /post/17 形式的链接) + if err != nil { + if id, convErr := strconv.Atoi(slug); convErr == nil { + err = query.Where("id = ?", id).First(&post).Error + } + } if err != nil { c.HTML(http.StatusNotFound, "index", gin.H{ @@ -209,6 +215,43 @@ func PostView(c *gin.Context) { var commentCount int64 database.DB.Model(&models.Comment{}).Where("post_id = ? AND status = ?", post.ID, "approved").Count(&commentCount) + // 读取评论展示设置 + showComments := true + if v, optErr := models.GetOptionValue(database.DB, OptionShowComments, 0); optErr == nil { + showComments = v != "0" + } + commentPageSize := 10 + if v, optErr := models.GetOptionValue(database.DB, OptionCommentPageSize, 0); optErr == nil { + if n, convErr := strconv.Atoi(v); convErr == nil && n > 0 { + commentPageSize = n + } + } + + // 顶级评论分页查询(回复随父评论一起展示,不单独分页) + commentPage, _ := strconv.Atoi(c.DefaultQuery("cpage", "1")) + if commentPage < 1 { + commentPage = 1 + } + var comments []models.Comment + commentTotalPages := 0 + if showComments { + var topCount int64 + database.DB.Model(&models.Comment{}). + Where("post_id = ? AND status = ? AND parent_id IS NULL", post.ID, "approved"). + Count(&topCount) + commentTotalPages = int(math.Ceil(float64(topCount) / float64(commentPageSize))) + if commentTotalPages > 0 && commentPage > commentTotalPages { + commentPage = commentTotalPages + } + database.DB.Where("post_id = ? AND status = ? AND parent_id IS NULL", post.ID, "approved"). + Preload("Children", func(db *gorm.DB) *gorm.DB { + return db.Where("status = ?", "approved") + }). + Order("created_at ASC"). + Offset((commentPage - 1) * commentPageSize).Limit(commentPageSize). + Find(&comments) + } + // 获取上一篇(发布时间更早的文章) var prevPost models.Post prevErr := database.DB.Where("status = ? AND published_at < ?", "published", post.PublishedAt). @@ -223,12 +266,11 @@ func PostView(c *gin.Context) { siteName := cfg.App.Name siteDesc := cfg.App.Description - var option models.Option - if err := database.DB.Where("name = ? AND user_id = ?", "site_name", 0).First(&option).Error; err == nil { - siteName = option.Value + if v, err := models.GetOptionValue(database.DB, "site_name", 0); err == nil { + siteName = v } - if err := database.DB.Where("name = ? AND user_id = ?", "site_desc", 0).First(&option).Error; err == nil { - siteDesc = option.Value + if v, err := models.GetOptionValue(database.DB, "site_desc", 0); err == nil { + siteDesc = v } // 加载侧边栏数据 @@ -241,9 +283,13 @@ func PostView(c *gin.Context) { Description: post.Summary, Post: post, Pages: pages, - Comments: post.Comments, + Comments: comments, CommentCount: commentCount, Year: time.Now().Year(), + // 评论展示设置与分页 + ShowComments: showComments, + CommentPage: commentPage, + CommentTotalPages: commentTotalPages, // Sidebar 数据 Categories: categories, Tags: tags, @@ -306,14 +352,12 @@ func PageView(c *gin.Context) { siteName := cfg.App.Name siteDesc := cfg.App.Description - var option models.Option - if err := database.DB.Where("name = ? AND user_id = ?", "site_name", 0).First(&option).Error; err == nil { - siteName = option.Value + if v, err := models.GetOptionValue(database.DB, "site_name", 0); err == nil { + siteName = v } - if err := database.DB.Where("name = ? AND user_id = ?", "site_desc", 0).First(&option).Error; err == nil { - siteDesc = option.Value + if v, err := models.GetOptionValue(database.DB, "site_desc", 0); err == nil { + siteDesc = v } - // 加载侧边栏数据 categories, tags, postCount, categoryCount, tagCount := loadSidebarData() diff --git a/models/models.go b/models/models.go index 6a06e1f..498cff0 100644 --- a/models/models.go +++ b/models/models.go @@ -226,12 +226,9 @@ func GetOptionsByUser(db *gorm.DB, userID uint) (map[string]string, error) { func BatchSetOptions(db *gorm.DB, userID uint, options map[string]string) error { return db.Transaction(func(tx *gorm.DB) error { for name, value := range options { - opt := Option{ - Name: name, - UserID: userID, - Value: value, - } - if err := tx.Save(&opt).Error; err != nil { + // 复合主键在 SQLite 旧表上可能缺失唯一约束,Save 会重复插入, + // 改用查找-更新-不存在才插入的方式保证幂等 + if err := SetOptionValue(tx, name, userID, value); err != nil { slog.Error("保存配置失败", "name", name, "error", err) return err } diff --git a/routers/router.go b/routers/router.go index 361a409..a87a308 100644 --- a/routers/router.go +++ b/routers/router.go @@ -95,7 +95,7 @@ func SetupRoutes(r *gin.Engine, cfg interface{}) { admin.DELETE("/pages/:id", handlers.DeletePage) // 评论管理 - admin.GET("/admin/comments", handlers.GetComments) + admin.GET("/comments", handlers.GetComments) admin.PUT("/comments/:id/approve", handlers.ApproveComment) admin.PUT("/comments/:id/spam", handlers.MarkSpamComment) admin.DELETE("/comments/:id", handlers.DeleteComment) @@ -107,6 +107,10 @@ func SetupRoutes(r *gin.Engine, cfg interface{}) { admin.PUT("/users/:id/password", handlers.AdminResetPassword) admin.DELETE("/users/:id", handlers.DeleteUser) + // 基础设置 + admin.GET("/settings", handlers.GetSettings) + admin.PUT("/settings", handlers.UpdateSettings) + // 文件管理(图片上传) admin.POST("/upload", handlers.UploadImage) admin.GET("/uploads", handlers.ListUploads) diff --git a/static/admin/app.js b/static/admin/app.js index 42fd03a..4ee043d 100644 --- a/static/admin/app.js +++ b/static/admin/app.js @@ -46,6 +46,9 @@ document.addEventListener('DOMContentLoaded', () => { // 用户表单 document.getElementById('user-form').addEventListener('submit', handleUserSubmit); + // 基础设置表单 + document.getElementById('settings-form').addEventListener('submit', handleSettingsSubmit); + // 封面图上传 const coverFileInput = document.getElementById('post-cover-file'); if (coverFileInput) { @@ -116,6 +119,9 @@ function showPage(pageName) { case 'themes': loadThemes(); break; + case 'settings': + loadSettings(); + break; case 'theme-editor': // 主题编辑器由 editTheme() 函数加载 break; @@ -486,18 +492,24 @@ async function loadPages() { } } -// 加载评论 -async function loadComments() { +// 加载评论(postId 为空时加载全部) +async function loadComments(postId = '') { try { - const res = await fetch(`${API_BASE}/comments`, { headers: getHeaders() }); + const url = postId ? `${API_BASE}/admin/comments?post_id=${postId}` : `${API_BASE}/admin/comments`; + const res = await fetch(url, { headers: getHeaders() }); const data = await res.json(); + + // 加载全部评论时刷新文章筛选下拉框 + if (!postId) { + populateCommentPostFilter(data.data || []); + } const tbody = document.getElementById('comments-list'); tbody.innerHTML = data.data?.map(c => ` ${c.author} ${c.content.substring(0, 50)}${c.content.length > 50 ? '...' : ''} - 文章 #${c.post_id} + ${c.post_title || '文章 #' + c.post_id} ${c.status} ${new Date(c.created_at).toLocaleString()} @@ -752,6 +764,82 @@ async function loadUsers() { } } +// 填充评论筛选下拉框(所有有评论的文章) +function populateCommentPostFilter(comments) { + const select = document.getElementById('comment-post-filter'); + if (!select) return; + + // 按文章去重 + const posts = new Map(); + comments.forEach(c => { + if (!posts.has(c.post_id)) { + posts.set(c.post_id, c.post_title || ('文章 #' + c.post_id)); + } + }); + + select.innerHTML = ''; + posts.forEach((title, id) => { + const opt = document.createElement('option'); + opt.value = id; + opt.textContent = title; + opt.title = title; // 悬停显示完整标题 + select.appendChild(opt); + }); + select.value = ''; +} + +// 按选中文章过滤评论列表 +function filterCommentsByPost() { + loadComments(document.getElementById('comment-post-filter').value); +} + +// 加载基础设置 +async function loadSettings() { + try { + const res = await fetch(`${API_BASE}/admin/settings`, { headers: getHeaders() }); + const data = await res.json(); + if (!res.ok) { + alert(data.error || '加载设置失败'); + return; + } + const s = data.data || {}; + document.getElementById('setting-site-name').value = s.site_name || ''; + document.getElementById('setting-site-desc').value = s.site_desc || ''; + document.getElementById('setting-show-comments').checked = s.show_comments !== '0'; + document.getElementById('setting-comments-per-page').value = s.comments_per_page || '10'; + } catch (err) { + console.error('加载设置失败:', err); + } +} + +// 保存基础设置 +async function handleSettingsSubmit(e) { + e.preventDefault(); + + const body = { + site_name: document.getElementById('setting-site-name').value.trim(), + site_desc: document.getElementById('setting-site-desc').value.trim(), + show_comments: document.getElementById('setting-show-comments').checked, + comments_per_page: parseInt(document.getElementById('setting-comments-per-page').value, 10) + }; + + try { + const res = await fetch(`${API_BASE}/admin/settings`, { + method: 'PUT', + headers: getHeaders(), + body: JSON.stringify(body) + }); + const data = await res.json(); + if (res.ok) { + alert('设置已保存'); + } else { + alert(data.error || '保存失败'); + } + } catch (err) { + alert('保存失败: ' + err.message); + } +} + let usersCache = []; // 显示新建用户表单 diff --git a/static/admin/index.html b/static/admin/index.html index 5dd54ae..5631b1a 100644 --- a/static/admin/index.html +++ b/static/admin/index.html @@ -24,6 +24,7 @@ 用户管理 文件管理 主题管理 + 基础设置 -
+{{if .ShowComments}} +

评论 ({{.CommentCount}})

@@ -112,5 +113,18 @@

暂无评论,来发表第一条评论吧!

{{end}} + + {{if gt .CommentTotalPages 1}} + + {{end}}
{{end}} +{{end}} diff --git a/templates/minimal/post.html b/templates/minimal/post.html index e09c821..868c580 100644 --- a/templates/minimal/post.html +++ b/templates/minimal/post.html @@ -53,7 +53,8 @@ -
+{{if .ShowComments}} +

评论 ({{.CommentCount}})

@@ -106,5 +107,18 @@

暂无评论,来发表第一条评论吧!

{{end}} + + {{if gt .CommentTotalPages 1}} + + {{end}}
{{end}} +{{end}} diff --git a/templates/mytheme/post.html b/templates/mytheme/post.html index 0f7bf3b..216bc7a 100644 --- a/templates/mytheme/post.html +++ b/templates/mytheme/post.html @@ -59,7 +59,8 @@ -
+{{if .ShowComments}} +

评论 ({{.CommentCount}})

@@ -112,5 +113,18 @@

暂无评论,来发表第一条评论吧!

{{end}} + + {{if gt .CommentTotalPages 1}} + + {{end}}
{{end}} +{{end}}