整理router,删除多余中单件和路由。退出按键放最上方。
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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, ""
|
||||
}
|
||||
|
||||
// 首页数据结构
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user