From 16ba817ecf2b981ca253beec34b47da5f200fa73 Mon Sep 17 00:00:00 2001 From: Zhang Chao Date: Tue, 25 Aug 2026 17:54:54 +0800 Subject: [PATCH] =?UTF-8?q?=E6=95=B4=E7=90=86router=EF=BC=8C=E5=88=A0?= =?UTF-8?q?=E9=99=A4=E5=A4=9A=E4=BD=99=E4=B8=AD=E5=8D=95=E4=BB=B6=E5=92=8C?= =?UTF-8?q?=E8=B7=AF=E7=94=B1=E3=80=82=E9=80=80=E5=87=BA=E6=8C=89=E9=94=AE?= =?UTF-8?q?=E6=94=BE=E6=9C=80=E4=B8=8A=E6=96=B9=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- handlers/backend/admin_view.go | 4 +-- handlers/backend/auth.go | 28 ++-------------- handlers/backend/user.go | 8 +---- handlers/frontend/view.go | 18 +++++++--- middleware/auth.go | 25 -------------- models/models.go | 1 - routers/router.go | 60 ++++++++++++++-------------------- static/css/adminstyle.css | 52 +++++++++++------------------ templates/admin/index.html | 15 ++++----- 9 files changed, 70 insertions(+), 141 deletions(-) diff --git a/handlers/backend/admin_view.go b/handlers/backend/admin_view.go index ee85a64..b919231 100644 --- a/handlers/backend/admin_view.go +++ b/handlers/backend/admin_view.go @@ -58,7 +58,7 @@ func AdminLoginView(c *gin.Context) { } // AdminView 后台管理页面:校验登录态后按角色用模板渲染菜单与页面框架 -// 依赖 OptionalAuth 中间件注入 userID/role 等上下文 +// 依赖 JWTAuth 中间件注入 userID/role 等上下文 func AdminView(c *gin.Context) { userID, exists := c.Get("userID") if !exists { @@ -68,7 +68,7 @@ func AdminView(c *gin.Context) { // 校验用户当前状态(禁用账号不允许进入后台) var user models.User - if err := database.DB.First(&user, userID).Error; err != nil || user.Status == 0 || !user.IsActive { + if err := database.DB.First(&user, userID).Error; err != nil || user.Status == 0 { c.Redirect(http.StatusFound, "/admin/login") return } diff --git a/handlers/backend/auth.go b/handlers/backend/auth.go index 7fd0452..1515fc4 100644 --- a/handlers/backend/auth.go +++ b/handlers/backend/auth.go @@ -73,14 +73,6 @@ type LoginRequest struct { Password string `json:"password" binding:"required"` } -// 注册请求 -type RegisterRequest struct { - Username string `json:"username" binding:"required,min=3,max=50"` - Password string `json:"password" binding:"required,min=6"` - Nickname string `json:"nickname"` - Email string `json:"email" binding:"required,email"` -} - // JWT Claims type Claims struct { UserID uint `json:"user_id"` @@ -103,7 +95,7 @@ func Login(c *gin.Context) { return } - if user.Status == 0 || !user.IsActive { + if user.Status == 0 { c.JSON(http.StatusForbidden, gin.H{"error": "账号已被禁用"}) return } @@ -137,24 +129,10 @@ func Login(c *gin.Context) { return } - // 同时写入 Cookie,供服务端渲染后台页面时识别登录态 + // 写入 Cookie,供服务端渲染后台页面时识别登录态 c.SetCookie("token", tokenString, 7*24*3600, "/", "", false, true) - c.JSON(http.StatusOK, gin.H{ - "token": tokenString, - "user": gin.H{ - "id": user.ID, - "username": user.Username, - "nickname": user.Nickname, - "email": user.Email, - "role": user.Role, - "avatar": user.Avatar, - "last_login_at": user.LastLoginAt, - "login_count": user.LoginCount, - }, - // 菜单由登录接口根据角色返回 - "menus": GetMenusByRole(user.Role), - }) + c.JSON(http.StatusOK, gin.H{"message": "登录成功"}) } // 退出登录(清除服务端 Cookie) diff --git a/handlers/backend/user.go b/handlers/backend/user.go index e6c4ca5..00d807a 100644 --- a/handlers/backend/user.go +++ b/handlers/backend/user.go @@ -92,11 +92,6 @@ func CreateUser(c *gin.Context) { return } - role := req.Role - if role != "admin" { - role = "user" - } - status := 1 if req.Status != nil { status = *req.Status @@ -106,9 +101,8 @@ func CreateUser(c *gin.Context) { Username: req.Username, Nickname: req.Nickname, Email: req.Email, - Role: role, + Role: req.Role, Status: status, - IsActive: true, } if err := user.SetPassword(req.Password); err != nil { diff --git a/handlers/frontend/view.go b/handlers/frontend/view.go index 7728276..b18f78a 100644 --- a/handlers/frontend/view.go +++ b/handlers/frontend/view.go @@ -8,9 +8,11 @@ import ( "goblog/config" "goblog/database" + "goblog/handlers/backend" "goblog/models" "github.com/gin-gonic/gin" + "github.com/golang-jwt/jwt/v5" "gorm.io/gorm" ) @@ -54,12 +56,20 @@ func loadSidebarData() (categories []models.Category, tags []models.Tag, postCou // loginInfo 从上下文提取登录态信息(供侧边栏系统菜单区分已登录/未登录) func loginInfo(c *gin.Context) (isLoggedIn bool, loginName string) { - if _, ok := c.Get("userID"); !ok { + tokenString, _ := c.Cookie("token") + if tokenString == "" { return false, "" } - name, _ := c.Get("username") - loginName, _ = name.(string) - return true, loginName + claims := &backend.Claims{} + + token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) { + return backend.JWTSecret(), nil + }) + + if err == nil && token.Valid { + return true, claims.Username + } + return false, "" } // 首页数据结构 diff --git a/middleware/auth.go b/middleware/auth.go index 58f8e85..5baba34 100644 --- a/middleware/auth.go +++ b/middleware/auth.go @@ -66,28 +66,3 @@ func AdminRequired() gin.HandlerFunc { c.Next() } } - -// OptionalAuth 可选认证中间件(用于某些既支持游客又支持登录用户的接口/页面) -func OptionalAuth() gin.HandlerFunc { - return func(c *gin.Context) { - tokenString := tokenFromContext(c) - if tokenString == "" { - c.Next() - return - } - - claims := &backend.Claims{} - - token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) { - return backend.JWTSecret(), nil - }) - - if err == nil && token.Valid { - c.Set("userID", claims.UserID) - c.Set("username", claims.Username) - c.Set("role", claims.Role) - } - - c.Next() - } -} diff --git a/models/models.go b/models/models.go index e740ac0..555a2ff 100644 --- a/models/models.go +++ b/models/models.go @@ -32,7 +32,6 @@ type User struct { Avatar string `gorm:"size:255" json:"avatar"` Role string `gorm:"size:20;default:'user'" json:"role"` // admin, user Status int `gorm:"default:1" json:"status"` // 1:启用 0:禁用 - IsActive bool `gorm:"default:true" json:"is_active"` // 账号激活状态 LastLoginAt *time.Time `json:"last_login_at"` // 最后登录时间 LoginCount int `gorm:"default:0" json:"login_count"` // 登录次数统计 } diff --git a/routers/router.go b/routers/router.go index 455fd23..6394adb 100644 --- a/routers/router.go +++ b/routers/router.go @@ -32,15 +32,15 @@ func setupStaticRoutes(r *gin.Engine) { // setupAdminPageRoutes 后台管理页面(服务端渲染) func setupAdminPageRoutes(r *gin.Engine) { r.GET(adminPath+"/login", backend.AdminLoginView) - r.GET(adminPath, middleware.OptionalAuth(), backend.AdminView) + r.GET(adminPath, middleware.JWTAuth(), backend.AdminView) } // setupFrontendRoutes 前台页面(服务端渲染) func setupFrontendRoutes(r *gin.Engine) { - r.GET("/", middleware.OptionalAuth(), frontend.IndexView) - r.GET("/post/:slug", middleware.OptionalAuth(), frontend.PostView) - r.GET("/page/:slug", middleware.OptionalAuth(), frontend.PageView) - r.GET("/search", middleware.OptionalAuth(), frontend.SearchView) + r.GET("/", frontend.IndexView) + r.GET("/post/:slug", frontend.PostView) + r.GET("/page/:slug", frontend.PageView) + r.GET("/search", frontend.SearchView) r.GET("/posts/:postID/comments", frontend.ListComments) r.POST("/posts/:postID/comment", frontend.SubmitComment) @@ -50,23 +50,10 @@ func setupFrontendRoutes(r *gin.Engine) { // setupAPIRoutes API 路由分组 func setupAPIRoutes(r *gin.Engine) { api := r.Group(apiPrefix) - - setupPublicAPIRoutes(api) // 公开接口 - setupAuthenticatedAPIRoutes(api) // 需要 JWT 的接口 - setupAdminAPIRoutes(api) // 管理员专属接口 -} - -// setupPublicAPIRoutes 公开 API(无需登录) -func setupPublicAPIRoutes(api *gin.RouterGroup) { // 登录 api.POST("/auth/login", backend.Login) - - // 仪表盘公开统计数据(可选认证,登录后返回更多数据) - api.GET("/dashboard/stats", middleware.OptionalAuth(), backend.GetDashboardStats) - - // 主题公开信息 - api.GET("/themes", backend.GetThemes) - api.GET("/theme", backend.GetCurrentTheme) + setupAuthenticatedAPIRoutes(api) // 需要 JWT 的接口 + setupAdminAPIRoutes(api) // 管理员专属接口 } // setupAuthenticatedAPIRoutes 需要 JWT 的接口(所有登录用户) @@ -74,9 +61,13 @@ func setupAuthenticatedAPIRoutes(api *gin.RouterGroup) { auth := api.Group("/") auth.Use(middleware.JWTAuth()) + // 仪表盘公开统计数据(可选认证,登录后返回更多数据) + auth.GET("/dashboard/stats", backend.GetDashboardStats) // 当前用户信息 auth.GET("/auth/me", backend.GetCurrentUser) + //TODO: 修改用户信息接口,在admin/index.html中添加修改用户信息表单 auth.PUT("/auth/me", backend.UpdateUser) + //TODO: 修改密码接口,在admin/index.html中添加修改密码表单 auth.PUT("/auth/password", backend.ChangePassword) auth.POST("/auth/logout", backend.Logout) @@ -87,17 +78,8 @@ func setupAuthenticatedAPIRoutes(api *gin.RouterGroup) { auth.PUT("/posts/:id", backend.UpdatePost) auth.DELETE("/posts/:id", backend.DeletePost) - // 分类读取(写入在管理员组) + // 分类读取 auth.GET("/categories", backend.GetCategories) - auth.GET("/categories/:id", backend.GetCategory) - - // 标签读取(写入在管理员组) - auth.GET("/tags", backend.GetTags) - auth.GET("/tags/:id", backend.GetTag) - - // 页面读取(写入在管理员组) - auth.GET("/pages", backend.GetPages) - auth.GET("/pages/:id", backend.GetPage) // 评论管理(查看所有评论,需登录) auth.GET("/comments", backend.GetComments) @@ -116,12 +98,6 @@ func setupAuthenticatedAPIRoutes(api *gin.RouterGroup) { auth.GET("/uploads", backend.ListUploads) auth.DELETE("/uploads/:id", backend.DeleteUpload) - // 主题管理(登录用户可切换主题、创建主题、编辑文件) - auth.POST("/theme", backend.SwitchTheme) - auth.POST("/themes/create", backend.CreateTheme) - auth.GET("/themes/:theme/files", backend.GetThemeFiles) - auth.GET("/themes/:theme/files/:file", backend.GetThemeFile) - auth.PUT("/themes/:theme/files/:file", backend.SaveThemeFile) } // setupAdminAPIRoutes 管理员专属接口(JWT + AdminRequired) @@ -131,11 +107,13 @@ func setupAdminAPIRoutes(api *gin.RouterGroup) { // 分类管理(写入) admin.POST("/categories", backend.CreateCategory) + admin.GET("/categories/:id", backend.GetCategory) admin.PUT("/categories/:id", backend.UpdateCategory) admin.DELETE("/categories/:id", backend.DeleteCategory) // 标签管理(写入) admin.POST("/tags", backend.CreateTag) + admin.GET("/tags/:id", backend.GetTag) admin.PUT("/tags/:id", backend.UpdateTag) admin.DELETE("/tags/:id", backend.DeleteTag) @@ -165,4 +143,14 @@ func setupAdminAPIRoutes(api *gin.RouterGroup) { admin.POST("/upload", backend.UploadImage) // 管理员上传(也可在认证组) admin.GET("/uploads", backend.ListUploads) admin.DELETE("/uploads/:id", backend.DeleteUpload) + + // 主题管理(登录用户可切换主题、创建主题、编辑文件) + // 主题公开信息 + admin.GET("/themes", backend.GetThemes) + admin.GET("/theme", backend.GetCurrentTheme) + admin.POST("/theme", backend.SwitchTheme) + admin.POST("/themes/create", backend.CreateTheme) + admin.GET("/themes/:theme/files", backend.GetThemeFiles) + admin.GET("/themes/:theme/files/:file", backend.GetThemeFile) + admin.PUT("/themes/:theme/files/:file", backend.SaveThemeFile) } diff --git a/static/css/adminstyle.css b/static/css/adminstyle.css index 9b18b19..73be5f2 100644 --- a/static/css/adminstyle.css +++ b/static/css/adminstyle.css @@ -22,20 +22,27 @@ body { display: flex; flex-direction: column; } - -.logo { - padding: 20px; - border-bottom: 1px solid rgba(255,255,255,0.1); +.admin-header { + padding: 15px 20px; + display: flex; + justify-content: space-between; + background-color: #001529; + color: #fff; + align-items: center; +} +.admin-header #username { + margin-left: auto; +} +.admin-header span { + background: transparent; + padding: 5px 12px; + border-radius: 4px; + color: #fff; + cursor: pointer; } -.logo h2 { - font-size: 20px; -} - -.logo p { - font-size: 12px; - color: rgba(255,255,255,0.6); - margin-top: 5px; +.admin-header span:hover { + background: rgba(255,255,255,0.1); } .menu { @@ -97,27 +104,6 @@ body { background: #1890ff; } -.user-info { - padding: 15px 20px; - border-top: 1px solid rgba(255,255,255,0.1); - display: flex; - justify-content: space-between; - align-items: center; -} - -.user-info button { - background: transparent; - border: 1px solid rgba(255,255,255,0.3); - color: #fff; - padding: 5px 12px; - border-radius: 4px; - cursor: pointer; -} - -.user-info button:hover { - background: rgba(255,255,255,0.1); -} - /* 主内容区 */ .main-content { flex: 1; diff --git a/templates/admin/index.html b/templates/admin/index.html index 9e4886c..476c156 100644 --- a/templates/admin/index.html +++ b/templates/admin/index.html @@ -8,12 +8,15 @@ +
+

GoBlog

+ {{.DisplayName}} + 退出 +
+
-
-

GoBlog

-

后台管理

-
+
{{range .Menus}}{{if eq .Group ""}}{{range $i, $item := .Items}}{{$item.Name}} {{end}}{{else}} @@ -26,10 +29,6 @@
{{end}}{{end}} -
- {{.DisplayName}} - -