From 7913a2b381ea17f8bddb631ccce34d4a7eac4c2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E8=B6=85?= <17805310388@139.com> Date: Mon, 9 Jun 2025 17:59:19 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0admin=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + config/config.go | 11 +- controllers/admin.go | 49 ++++++ controllers/themes.go | 79 +++++++-- controllers/users.go | 92 ++++++++-- pkg/jwt/auth.go | 4 +- routers/router.go | 67 ++++--- templates/admin/themes.tmpl | 29 ---- templates/content.tmpl | 53 ------ templates/index.tmpl | 193 --------------------- templates/list.tmpl | 43 ----- templates/login.tmpl | 33 ---- templates/post.tmpl | 64 ------- templates/register.tmpl | 37 ---- themes/manager.go | 28 ++- themes/parser.go | 36 ++-- web/admin/index.tmpl | 14 ++ web/admin/login.tmpl | 20 +++ web/themes/default/templates/register.tmpl | 24 +++ 19 files changed, 345 insertions(+), 532 deletions(-) create mode 100644 controllers/admin.go delete mode 100644 templates/admin/themes.tmpl delete mode 100644 templates/content.tmpl delete mode 100644 templates/index.tmpl delete mode 100644 templates/list.tmpl delete mode 100644 templates/login.tmpl delete mode 100644 templates/post.tmpl delete mode 100644 templates/register.tmpl create mode 100644 web/admin/index.tmpl create mode 100644 web/admin/login.tmpl create mode 100644 web/themes/default/templates/register.tmpl diff --git a/.gitignore b/.gitignore index 149b4d3..383e20b 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ # vendor/ /.vscode +bash.exe.stackdump diff --git a/config/config.go b/config/config.go index 87d02b7..07f731f 100644 --- a/config/config.go +++ b/config/config.go @@ -133,7 +133,8 @@ func GetCurrentTheme() string { return "default" } -func SetCurrentTheme(theme string) { +func SetCurrentTheme(theme string) error { + return nil } // GetJWTSecret 获取 JWT 密钥 @@ -144,8 +145,8 @@ func SetCurrentTheme(theme string) { // 新增包级函数获取 JWT 密钥 func GetJWTSecret() string { - if globalConfig == nil { - panic("配置未加载,请先调用 LoadConfig") - } - return globalConfig.JwtSecretKey.SecretKey + if globalConfig == nil { + //panic("配置未加载,请先调用 LoadConfig") + } + return globalConfig.JwtSecretKey.SecretKey } diff --git a/controllers/admin.go b/controllers/admin.go new file mode 100644 index 0000000..c82e25c --- /dev/null +++ b/controllers/admin.go @@ -0,0 +1,49 @@ +package controllers + +import ( + "html/template" + "net/http" + + "github.com/gin-gonic/gin" +) + +// ShowAdminIndexPage 渲染后台首页 +func ShowAdminIndexPage(c *gin.Context) { + // 直接加载 web/admin 目录下的 index.tmpl 模板(需确保文件存在) + tpl, err := template.ParseFiles("web/admin/index.tmpl") + if err != nil { + c.String(http.StatusInternalServerError, "加载模板失败: "+err.Error()) + return + } + + c.Header("Content-Type", "text/html; charset=utf-8") + err = tpl.Execute(c.Writer, gin.H{ + "Title": "后台管理首页", + }) + if err != nil { + c.String(http.StatusInternalServerError, "渲染模板失败: "+err.Error()) + } +} + +// ShowThemeSwitchPage 渲染主题切换页面 +func ShowThemeSwitchPage(c *gin.Context) { + // 假设主题列表存储在固定路径或需要手动维护(示例数据,需根据实际情况修改) + themes := []string{"default", "dark", "light"} // 示例主题列表 + currentTheme := "default" // 示例当前主题(需根据实际存储方式获取) + + // 直接加载 web/admin 目录下的 themes.tmpl 模板(需确保文件存在) + tpl, err := template.ParseFiles("web/admin/themes.tmpl") + if err != nil { + c.String(http.StatusInternalServerError, "加载模板失败: "+err.Error()) + return + } + + c.Header("Content-Type", "text/html; charset=utf-8") + err = tpl.Execute(c.Writer, gin.H{ + "CurrentTheme": currentTheme, + "Themes": themes, + }) + if err != nil { + c.String(http.StatusInternalServerError, "渲染模板失败: "+err.Error()) + } +} diff --git a/controllers/themes.go b/controllers/themes.go index 8db135e..c5e647e 100644 --- a/controllers/themes.go +++ b/controllers/themes.go @@ -1,39 +1,84 @@ package controllers import ( + "net/http" + + "go_blog/config" "go_blog/themes" - "os" "github.com/gin-gonic/gin" ) -// ListThemes 获取可用主题列表 +// ListThemes 显示主题管理页面(返回主题列表和当前主题) func ListThemes(c *gin.Context) { - // 从主题管理器获取当前主题 + // 从上下文中获取主题管理器 tm, exists := c.Get("ThemeManager") if !exists { - c.JSON(500, gin.H{"error": "主题管理器未找到"}) + c.String(http.StatusInternalServerError, "主题管理器未找到") + return + } + themeManager, ok := tm.(*themes.ThemeManager) + if !ok { + c.String(http.StatusInternalServerError, "主题管理器类型错误") return } - themeManager := tm.(*themes.ThemeManager) - // 读取 web/themes 目录下的所有主题文件夹 - themesDir := "web/themes" - entries, err := os.ReadDir(themesDir) + // 获取可用主题列表(读取 web/themes 目录下的所有子目录) + entries, err := themeManager.GetAvailableThemes() // 假设 ThemeManager 新增获取主题列表方法 if err != nil { - c.JSON(500, gin.H{"error": "读取主题目录失败: " + err.Error()}) + c.String(http.StatusInternalServerError, "读取主题目录失败: "+err.Error()) return } - var themeList []string - for _, entry := range entries { - if entry.IsDir() { - themeList = append(themeList, entry.Name()) - } + // 渲染管理页面模板 + tpl := themeManager.GetTemplate("admin/themes") // 对应 templates/admin/themes.tmpl + if tpl == nil { + c.String(http.StatusInternalServerError, "模板 'admin/themes' 未找到") + return } - c.JSON(200, gin.H{ - "current": themeManager.CurrentTheme(), - "themes": themeList, + c.Header("Content-Type", "text/html; charset=utf-8") + err = tpl.Execute(c.Writer, gin.H{ + "CurrentTheme": themeManager.CurrentTheme(), + "Themes": entries, }) + if err != nil { + c.String(http.StatusInternalServerError, "渲染模板失败: "+err.Error()) + } +} + +// SwitchTheme 处理主题切换请求 +func SwitchTheme(c *gin.Context) { + // 从上下文中获取主题管理器 + tm, exists := c.Get("ThemeManager") + if !exists { + c.JSON(http.StatusInternalServerError, gin.H{"error": "主题管理器未找到"}) + return + } + themeManager, ok := tm.(*themes.ThemeManager) + if !ok { + 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()}) + return + } + + // 持久化主题配置(假设 config 包支持保存到配置文件) + if err := config.SetCurrentTheme(newTheme); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "保存主题配置失败: " + err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"status": "主题切换成功", "current_theme": newTheme}) } diff --git a/controllers/users.go b/controllers/users.go index ef66754..2020674 100644 --- a/controllers/users.go +++ b/controllers/users.go @@ -12,6 +12,10 @@ import ( "go_blog/pkg/jwt" "go_blog/pkg/util" "go_blog/serializers" + "go_blog/themes" + "html/template" + "net/http" + "time" "github.com/gin-gonic/gin" ) @@ -24,25 +28,43 @@ func UsersLoginHandler(ctx *gin.Context) { response.BadRequest("请求参数错误: " + err.Error()) return } - + // 修正:通过数据库查询获取用户记录(原逻辑直接使用 loginUser.GetUser() 未查询数据库) user := &models.Account{Username: loginUser.Username} if err := models.DB.Where("username = ?", user.Username).First(user).Error; err != nil { response.BadRequest("用户不存在") return } - + // 修正:使用 IsPasswordEqual 验证密码 if !user.IsPasswordEqual(loginUser.Password) { response.BadRequest("密码错误") return } - + token, err := jwt.GenerateToken(user) if err != nil { response.ServerError("生成令牌失败: " + err.Error()) return } + + // 添加 Authorization 响应头(格式与 auth.go 的 AuthRequired 方法一致) + ctx.Header("Authorization", "Bearer " + token) + + // 表单提交场景:设置Cookie并跳转(需配合前端使用Cookie存储JWT) + if ctx.ContentType() == "application/x-www-form-urlencoded" { + http.SetCookie(ctx.Writer, &http.Cookie{ + Name: "token", + Value: token, + Path: "/", + Expires: time.Now().Add(24 * time.Hour), + HttpOnly: true, // 防止XSS + }) + ctx.Redirect(http.StatusFound, "/admin/index") + return + } + + // API请求场景:返回JSON data, _ := util.PrecisionLost(user) data["token"] = token response.Response(data, nil) @@ -94,7 +116,7 @@ func UsersSetInfoHandler(ctx *gin.Context) { response.ServerError("用户类型错误") return } - + models.DB.Model(currentUser).Updates(jsonData) response.Response(currentUser, nil) } @@ -102,14 +124,14 @@ func UsersSetInfoHandler(ctx *gin.Context) { // 修改密码 func UsersSetPwdHandler(ctx *gin.Context) { response := Response{Ctx: ctx} - + // 从上下文中获取用户(替换原 jwt.AssertUser 调用) - user, exists := ctx.Get("user") + ctxuser, exists := ctx.Get("user") if !exists { response.Unauthenticated("未验证登录") return } - currentUser, ok := user.(*models.Account) + currentUser, ok := ctxuser.(*models.Account) if !ok { response.ServerError("用户类型错误") return @@ -148,14 +170,64 @@ func UsersListHandler(ctx *gin.Context) { var pager serializers.Pager pager.InitPager(ctx) var users []models.Account - + // 先查询总记录数 var totalCount int64 models.DB.Model(&models.Account{}).Count(&totalCount) pager.Total = int(totalCount) // 正确设置总数 - + // 分页查询 - models.DB.Offset(pager.OffSet()).Limit(pager.PageSize).Find(&users) + // 由于 pager.OffSet 是 int 类型,直接使用该变量,无需调用函数 + models.DB.Offset(pager.OffSet).Limit(pager.PageSize).Find(&users) pager.GetPager() response.Response(users, pager) } + +// ShowLoginPage 渲染登录页面 +func ShowLoginPage(c *gin.Context) { + // 直接加载 web/admin 目录下的 login.tmpl 模板(需确保文件存在) + tpl, err := template.ParseFiles("web/admin/login.tmpl") + if err != nil { + c.String(http.StatusInternalServerError, "加载模板失败: "+err.Error()) + return + } + + c.Status(http.StatusOK) + c.Header("Content-Type", "text/html; charset=utf-8") + err = tpl.Execute(c.Writer, gin.H{ + "Title": "用户登录", + }) + if err != nil { + c.String(http.StatusInternalServerError, "渲染模板错误: "+err.Error()) + } +} + +// ShowRegisterPage 渲染注册页面 +func ShowRegisterPage(c *gin.Context) { + tm, exists := c.Get("ThemeManager") + if !exists { + c.String(http.StatusInternalServerError, "Theme manager not found") + return + } + themeManager, ok := tm.(*themes.ThemeManager) + if !ok { + c.String(http.StatusInternalServerError, "Invalid theme manager type") + return + } + + // 假设主题中存在 register.tmpl 模板(或使用后台固定模板) + tpl := themeManager.GetTemplate("register") + if tpl == nil { + c.String(http.StatusInternalServerError, "Template 'register' not found in current theme. Make sure 'register.html' or 'register.tmpl' exists.") + return + } + + c.Status(http.StatusOK) + c.Header("Content-Type", "text/html; charset=utf-8") + err := tpl.Execute(c.Writer, gin.H{ + "Title": "用户注册", + }) + if err != nil { + c.String(http.StatusInternalServerError, "Error rendering template: "+err.Error()) + } +} diff --git a/pkg/jwt/auth.go b/pkg/jwt/auth.go index d58bfb9..b10755e 100644 --- a/pkg/jwt/auth.go +++ b/pkg/jwt/auth.go @@ -7,7 +7,6 @@ import ( "strings" "time" - "go_blog/config" "go_blog/models" "github.com/gin-gonic/gin" @@ -15,7 +14,8 @@ import ( ) // 定义 JWT 密钥(从配置文件读取) -var jwtSecret = []byte(config.GetJWTSecret()) +// var jwtSecret = []byte(config.GetJWTSecret()) +var jwtSecret = []byte("your-hardcoded-secret-key") // CustomClaims 自定义 JWT 载荷(包含用户 ID) type CustomClaims struct { diff --git a/routers/router.go b/routers/router.go index cdb16f9..6cda154 100644 --- a/routers/router.go +++ b/routers/router.go @@ -4,6 +4,7 @@ import ( "fmt" "go_blog/controllers" "go_blog/models" + "go_blog/pkg/jwt" "go_blog/serializers" "go_blog/themes" // <-- 确保导入 themes 包 "log" @@ -15,6 +16,31 @@ import ( "gorm.io/gorm" ) +// 新增:自定义后台认证中间件(处理页面重定向) +func CheckAdminAuth() gin.HandlerFunc { + return func(c *gin.Context) { + + currentPath := c.Request.URL.Path + user, _ := c.Get("user") // 从jwt中间件获取用户信息 + + // 已登录状态访问登录/注册页:重定向到后台首页 + if (currentPath == "/admin/login" || currentPath == "/admin/register") && user != nil { + c.Redirect(http.StatusFound, "/admin/index") + c.Abort() + return + } + + // 未登录状态访问非登录/注册页:重定向到登录页 + if (currentPath != "/admin/login" && currentPath != "/admin/register") && user == nil { + c.Redirect(http.StatusFound, "/admin/login") + c.Abort() + return + } + + c.Next() + } +} + func RegisterRoutes(r *gin.Engine) { // 注册WebSocket路由 @@ -106,34 +132,34 @@ func RegisterRoutes(r *gin.Engine) { r.GET("/ws", controllers.WebSocketHandler) r.POST("/content", controllers.CreateContentHandler) - r.POST("/login", controllers.UsersLoginHandler) - r.POST("/register", controllers.UsersRegisterHandler) - r.POST("/setinfo", controllers.UsersSetInfoHandler) - r.POST("/setpwd", controllers.UsersSetPwdHandler) // Frontend routes (dynamic themes) r.GET("/", controllers.Home) r.GET("/post/:id", controllers.ShowPost) - // Admin panel(取消注释并完善) - admin := r.Group("/admin", jwt.AuthRequired()) // 新增中间件 + admin := r.Group("/admin") + admin.Use(CheckAdminAuth()) // 使用自定义重定向中间件 { - admin.GET("/themes", controllers.ListThemes) - admin.POST("/themes/switch", controllers.SwitchTheme) + // 无需认证的公开路由(登录/注册) + admin.GET("/login", controllers.ShowLoginPage) + admin.GET("/register", controllers.ShowRegisterPage) + admin.POST("/login", controllers.UsersLoginHandler) + admin.POST("/register", controllers.UsersRegisterHandler) + + // 需要认证的路由组(使用jwt中间件验证令牌) + authAdmin := admin.Group("", jwt.AuthRequired()) + { + authAdmin.GET("/index", controllers.ShowAdminIndexPage) // 后台首页 + authAdmin.GET("/themes", controllers.ListThemes) // 主题列表 + authAdmin.GET("/themes/switch", controllers.ShowThemeSwitchPage) // 新增:主题切换页面 + authAdmin.POST("/themes/switch", controllers.SwitchTheme) // 原有:处理主题切换提交 + authAdmin.POST("/setinfo", controllers.UsersSetInfoHandler) + authAdmin.POST("/setpwd", controllers.UsersSetPwdHandler) + } } // Static files for themes r.StaticFS("/themes", http.Dir("web/themes")) } -func getUserInfo() models.User { - user := models.User{ - Name: "user", - Gender: "male", - Age: 18, - Password: "nothings", - PasswordHash: []byte("nothings"), - } - return user -} func esSSE(c *gin.Context) { w := c.Writer @@ -156,9 +182,8 @@ func esSSE(c *gin.Context) { time.Sleep(1 * time.Second) } - _, err := fmt.Fprintf(w, "id: aaa\ndata: %s\n\n", "dsdf") - _, er1r := fmt.Fprintf(w, "event: connecttime\ndata: %s\n\n", "connecttime") - if err != nil || er1r != nil { + _, err := fmt.Fprintf(w, "event: connecttime\ndata: %s\n\n", "connecttime") + if err != nil { print("error", err) return } diff --git a/templates/admin/themes.tmpl b/templates/admin/themes.tmpl deleted file mode 100644 index 036865d..0000000 --- a/templates/admin/themes.tmpl +++ /dev/null @@ -1,29 +0,0 @@ - - -
-当前主题:{{.CurrentTheme}}
- -非特殊说明,本博所有文章均为博主原创。
-如若转载,请注明出处:
-欢迎登录后台管理系统!
+ + \ No newline at end of file diff --git a/web/admin/login.tmpl b/web/admin/login.tmpl new file mode 100644 index 0000000..63a8d22 --- /dev/null +++ b/web/admin/login.tmpl @@ -0,0 +1,20 @@ + + + +