diff --git a/admin/thememanager.go b/admin/thememanager.go deleted file mode 100644 index 81e09bf..0000000 --- a/admin/thememanager.go +++ /dev/null @@ -1,21 +0,0 @@ -package admin - -import ( - "go_blog/themes" - - "github.com/gin-gonic/gin" -) - -var themeManager = themes.NewManager("themes") -var router = gin.Default() - -// 切换主题(管理后台) -func switchTheme(c *gin.Context) { - newTheme := c.PostForm("theme") - if err := themeManager.LoadTheme(newTheme); err != nil { - c.JSON(500, gin.H{"error": err.Error()}) - return - } - themeManager.RegisterStaticRoutes(router) // 重新注册静态路由 - c.JSON(200, gin.H{"status": "success"}) -} diff --git a/config/config.go b/config/config.go index 6fa7499..28d7a96 100644 --- a/config/config.go +++ b/config/config.go @@ -20,12 +20,6 @@ const ( ) type DataBaseConfig struct { - Host string - Port string - User string - Password string - DBName string - Charset string Prefix string Driver string DSN string @@ -117,3 +111,8 @@ func LoadConfig(configPath string) (*Config, error) { globalConfig = &cfg // 保存配置到全局变量 return &cfg, nil } + +// GetGlobalConfig 获取全局配置 +func GetGlobalConfig() *Config { + return globalConfig +} diff --git a/config/config.yml b/config/config.yml index df0dc6e..7bfa478 100644 --- a/config/config.yml +++ b/config/config.yml @@ -1,13 +1,7 @@ database: Driver: mysql - Host: 47.93.160.42 - Port: 3630 - User: blog - Password: qI7=bL4@iJ - DBName: blog - Charset: utf8mb4 Prefix: gin_ - DSN: "mysql:mysql@tcp(47.93.160.42:3630)/gin_blog?charset=utf8mb4&parseTime=True&loc=Local" + DSN: "blog:qI7=bL4@iJ@tcp(47.93.160.42:3630)/blog?charset=utf8mb4&parseTime=True&loc=Local" # Prefix: gin_ # This line is commented out in the original .ini file jwt: diff --git a/controllers/themes.go b/controllers/themes.go index 7142d96..a6f02f8 100644 --- a/controllers/themes.go +++ b/controllers/themes.go @@ -5,6 +5,7 @@ import ( "net/http" "go_blog/config" + "go_blog/models" "go_blog/themes" "github.com/gin-gonic/gin" @@ -63,23 +64,38 @@ func SwitchTheme(c *gin.Context) { c.JSON(http.StatusInternalServerError, gin.H{"error": "主题管理器类型错误"}) return } - - // 获取前端提交的主题名称 newTheme := c.PostForm("theme") if newTheme == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "主题名称不能为空"}) return } - // 加载新主题 - if err := themeManager.LoadTheme(newTheme); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "加载主题失败: " + err.Error()}) + // 校验主题是否存在(保持原有逻辑) + availableThemes, _ := themeManager.GetAvailableThemes() + themeExists := false + for _, t := range availableThemes { + if t == newTheme { + themeExists = true + break + } + } + if !themeExists { + c.JSON(http.StatusBadRequest, gin.H{"error": "无效的主题名称"}) return } - // 持久化主题配置(假设 config 包支持保存到配置文件) - if err := config.SetCurrentTheme(newTheme); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "保存主题配置失败: " + err.Error()}) + // 加载新主题(保持原有逻辑) + if err := themeManager.LoadTheme(newTheme); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "主题加载失败,请检查主题文件"}) + return + } + // 更新全局配置 + globalConfig := config.GetGlobalConfig() + globalConfig.Theme.Current = newTheme + + // 调用options.go的SetOptionValue持久化配置 + if err := models.SetOptionValue(models.DB, "current_theme", 0, newTheme); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "主题配置保存失败: " + err.Error()}) return } diff --git a/go.mod b/go.mod index d3ebc9e..ebf672c 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,8 @@ require ( github.com/gorilla/websocket v1.5.1 golang.org/x/crypto v0.37.0 gorm.io/driver/mysql v1.5.6 + gorm.io/driver/postgres v1.6.0 + gorm.io/driver/sqlite v1.5.7 ) require ( @@ -22,8 +24,13 @@ require ( github.com/gorilla/securecookie v1.1.2 // indirect github.com/gorilla/sessions v1.4.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgx/v5 v5.6.0 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/magiconair/properties v1.8.7 // indirect + github.com/mattn/go-sqlite3 v1.14.22 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect @@ -35,6 +42,7 @@ require ( go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect + golang.org/x/sync v0.13.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect ) diff --git a/go.sum b/go.sum index 27d02ba..55f4fd8 100644 --- a/go.sum +++ b/go.sum @@ -49,6 +49,14 @@ github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/ github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= +github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= 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= @@ -69,6 +77,8 @@ github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0V github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= 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/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -127,6 +137,8 @@ golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8 golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= +golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= @@ -135,8 +147,8 @@ golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= @@ -144,6 +156,10 @@ 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/driver/mysql v1.5.6 h1:Ld4mkIickM+EliaQZQx3uOJDJHtrd70MxAUqWqlx3Y8= gorm.io/driver/mysql v1.5.6/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= +gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4= +gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo= +gorm.io/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I= +gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4= gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= diff --git a/models/init.go b/models/init.go index 5f8bcf9..1d7bf0d 100644 --- a/models/init.go +++ b/models/init.go @@ -7,7 +7,6 @@ package models import ( - "fmt" "go_blog/config" "go_blog/pkg/util" "log/slog" @@ -15,6 +14,8 @@ import ( "time" "gorm.io/driver/mysql" + "gorm.io/driver/postgres" + "gorm.io/driver/sqlite" "gorm.io/gorm" "gorm.io/gorm/schema" ) @@ -22,16 +23,20 @@ import ( var DB *gorm.DB func InitDatabase(conf *config.Config) { + var dialector gorm.Dialector + switch conf.DataBase.Driver { + case "mysql": + dialector = mysql.Open(conf.DataBase.DSN) + case "postgres": + dialector = postgres.Open(conf.DataBase.DSN) + case "sqlite": + dialector = sqlite.Open(conf.DataBase.DSN) + default: + slog.Error("不支持的数据库驱动", "driver", conf.DataBase.Driver) + os.Exit(1) + } - conUri := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=%s&parseTime=True&loc=Local", - conf.DataBase.User, - conf.DataBase.Password, - conf.DataBase.Host, - conf.DataBase.Port, - conf.DataBase.DBName, - conf.DataBase.Charset) - // 2. 初始化数据库 - db, err := gorm.Open(mysql.Open(conUri), &gorm.Config{ + db, err := gorm.Open(dialector, &gorm.Config{ NamingStrategy: schema.NamingStrategy{ TablePrefix: conf.DataBase.Prefix, }, diff --git a/models/options.go b/models/options.go new file mode 100644 index 0000000..8e7b363 --- /dev/null +++ b/models/options.go @@ -0,0 +1,42 @@ +package models + +import "gorm.io/gorm" + +// Option 对应 typecho_options 数据表 +// 用于存储系统配置项 +type Option struct { + Name string `gorm:"column:name;type:varchar(32);primaryKey"` // 配置名称(主键) + Value string `gorm:"column:value;type:text"` // 配置值 + User int `gorm:"column:user;type:int;default:0"` // 用户ID(0表示全局配置) +} + +// TableName 指定表名 +func (Option) TableName() string { + return "typecho_options" +} + +// GetOptionValue 根据name和user获取配置值 +// 返回:配置值,错误(无记录时返回gorm.ErrRecordNotFound) +func GetOptionValue(db *gorm.DB, name string, user int) (string, error) { + + var option Option + result := db.Where("name = ? AND user = ?", name, user).First(&option) + if result.Error != nil { + return "", result.Error // 可能返回gorm.ErrRecordNotFound或其他数据库错误 + } + return option.Value, nil +} + +// SetOptionValue 根据name和user设置配置值(存在则更新,不存在则新增) +func SetOptionValue(db *gorm.DB, name string, user int, value string) error { + // 构造要更新/插入的配置项 + option := Option{ + Name: name, + User: user, + Value: value, + } + + // 使用GORM的Save方法(自动判断是否存在,存在则更新,不存在则插入) + result := db.Save(&option) + return result.Error +} diff --git a/themes/manager.go b/themes/manager.go index 8088e37..b0329a8 100644 --- a/themes/manager.go +++ b/themes/manager.go @@ -46,25 +46,25 @@ var ( ) // 核心方法:加载主题 -func (m *ThemeManager) LoadTheme(themeName string) error { - m.mu.Lock() - defer m.mu.Unlock() +func (themeManager *ThemeManager) LoadTheme(themeName string) error { + themeManager.mu.Lock() + defer themeManager.mu.Unlock() // 1. 验证主题目录结构 - themePath := filepath.Join(m.themesDir, themeName) + themePath := filepath.Join(themeManager.themesDir, themeName) if !isValidTheme(themePath) { return fmt.Errorf("%w: %s", ErrInvalidThemeStructure, themeName) } // 2. 加载模板文件 - tpls, err := m.parseTemplates(themePath) + tpls, err := themeManager.parseTemplates(themePath) if err != nil { return fmt.Errorf("template parsing failed: %w", err) } // 3. 更新当前主题 - m.currentTheme = themeName - m.templates = tpls + themeManager.currentTheme = themeName + themeManager.templates = tpls return nil } @@ -229,3 +229,15 @@ func (m *ThemeManager) GetAvailableThemes() ([]string, error) { } return themes, nil } + +// 加载主题下所有模板(包括子模板) +func (tm *ThemeManager) LoadTemplates(themeName string) error { + themePath := filepath.Join("web", "themes", themeName, "templates") + // 使用通配符加载所有tmpl文件 + tmpl, err := template.ParseGlob(filepath.Join(themePath, "*.tmpl")) + if err != nil { + return fmt.Errorf("加载主题模板失败: %w", err) + } + tm.templates[themeName] = tmpl + return nil +} diff --git a/themes/parser.go b/themes/parser.go deleted file mode 100644 index 8e842d4..0000000 --- a/themes/parser.go +++ /dev/null @@ -1,45 +0,0 @@ -package themes // Declare the package at the top - -import ( - "fmt" - config "go_blog/config" // Adjust import path as needed - "net/http" - - "github.com/gin-gonic/gin" -) - -// RenderTemplate renders a template based on the current theme -func RenderTemplate(c *gin.Context, tmpl string, data gin.H) { - // Get the current theme - theme := config.GetCurrentTheme() - - // Construct the template path: "themes/default/home.html" - tplPath := fmt.Sprintf("themes/%s/%s", theme, tmpl) - - // Render the template using html/template - c.HTML(http.StatusOK, tplPath, data) -} - -// SwitchTheme handles POST requests to switch the current theme -// SwitchTheme 处理主题切换请求(修正后) -func SwitchTheme(c *gin.Context) { - // 从上下文中获取主题管理器(通过 main.go 的 themeMiddleware 注入) - tm, exists := c.Get("ThemeManager") - if !exists { - c.JSON(http.StatusInternalServerError, gin.H{"error": "主题管理器未找到"}) - return - } - themeManager, ok := tm.(*ThemeManager) - if !ok { - c.JSON(http.StatusInternalServerError, gin.H{"error": "主题管理器类型错误"}) - return - } - - newTheme := c.PostForm("theme") - if err := themeManager.LoadTheme(newTheme); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "加载主题失败: " + err.Error()}) - return - } - config.SetCurrentTheme(newTheme) // 持久化主题配置(需确保 config 包支持) - c.JSON(http.StatusOK, gin.H{"status": "主题切换成功"}) -} diff --git a/web/themes/default copy/templates/index.tmpl b/web/themes/default copy/templates/index.tmpl new file mode 100644 index 0000000..3921040 --- /dev/null +++ b/web/themes/default copy/templates/index.tmpl @@ -0,0 +1,164 @@ + + +
+非特殊说明,本博所有文章均为博主原创。
+如若转载,请注明出处:
+