整理router,删除多余中单件和路由。退出按键放最上方。

This commit is contained in:
2026-08-25 17:54:54 +08:00
parent 052803679a
commit 16ba817ecf
9 changed files with 70 additions and 141 deletions
+2 -2
View File
@@ -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
}
+3 -25
View File
@@ -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)
+1 -7
View File
@@ -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 {
+14 -4
View File
@@ -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, ""
}
// 首页数据结构
-25
View File
@@ -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()
}
}
-1
View File
@@ -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"` // 登录次数统计
}
+24 -36
View File
@@ -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)
}
+19 -33
View File
@@ -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;
+7 -8
View File
@@ -8,12 +8,15 @@
<link rel="stylesheet" href="/static/css/adminstyle.css?v=3">
</head>
<body>
<div class="admin-header">
<h2>GoBlog</h2>
<span id="username">{{.DisplayName}}</span>
<span id="logout-btn">退出</span>
</div>
<div id="app">
<aside class="sidebar">
<div class="logo">
<h2>GoBlog</h2>
<p>后台管理</p>
</div>
<nav class="menu">
{{range .Menus}}{{if eq .Group ""}}{{range $i, $item := .Items}}<a href="#{{$item.Key}}" class="menu-item{{if eq $item.Key "dashboard"}} active{{end}}" data-page="{{$item.Key}}">{{$item.Name}}</a>
{{end}}{{else}}
@@ -26,10 +29,6 @@
</div>
{{end}}{{end}}
</nav>
<div class="user-info">
<span id="username">{{.DisplayName}}</span>
<button id="logout-btn">退出</button>
</div>
</aside>
<main class="main-content">