ai优化,删除未使用代码,提取重复代码。
This commit is contained in:
+1
-16
@@ -2,8 +2,6 @@ package config
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/spf13/viper"
|
||||
@@ -82,18 +80,13 @@ func setDefaults() {
|
||||
|
||||
// parseConfig 解析配置到结构体
|
||||
func parseConfig() *Config {
|
||||
port := viper.GetInt("server.port")
|
||||
if port == 0 {
|
||||
port, _ = strconv.Atoi(getEnv("SERVER_PORT", "8080"))
|
||||
}
|
||||
|
||||
return &Config{
|
||||
Database: DatabaseConfig{
|
||||
Driver: viper.GetString("database.driver"),
|
||||
DSN: viper.GetString("database.dsn"),
|
||||
},
|
||||
Server: ServerConfig{
|
||||
Port: port,
|
||||
Port: viper.GetInt("server.port"),
|
||||
Mode: viper.GetString("server.mode"),
|
||||
},
|
||||
App: AppConfig{
|
||||
@@ -124,11 +117,3 @@ func watchConfig() {
|
||||
func GetGlobalConfig() *Config {
|
||||
return globalConfig
|
||||
}
|
||||
|
||||
// getEnv 获取环境变量
|
||||
func getEnv(key, defaultValue string) string {
|
||||
if value := os.Getenv(key); value != "" {
|
||||
return value
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
+4
-13
@@ -11,7 +11,6 @@ import (
|
||||
"goblog/models"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
)
|
||||
|
||||
// adminTemplatePath 后台单页模板(含 {{define}} 分块,按权限渲染)
|
||||
@@ -59,25 +58,17 @@ func AdminLoginView(c *gin.Context) {
|
||||
}
|
||||
|
||||
// AdminView 后台管理页面:校验登录态后按角色用模板渲染菜单与页面框架
|
||||
// 依赖 OptionalAuth 中间件注入 userID/role 等上下文
|
||||
func AdminView(c *gin.Context) {
|
||||
tokenString, err := c.Cookie("token")
|
||||
if err != nil || tokenString == "" {
|
||||
c.Redirect(http.StatusFound, "/admin/login")
|
||||
return
|
||||
}
|
||||
|
||||
claims := &Claims{}
|
||||
token, err := jwt.ParseWithClaims(tokenString, claims, func(t *jwt.Token) (interface{}, error) {
|
||||
return jwtSecret, nil
|
||||
})
|
||||
if err != nil || !token.Valid {
|
||||
userID, exists := c.Get("userID")
|
||||
if !exists {
|
||||
c.Redirect(http.StatusFound, "/admin/login")
|
||||
return
|
||||
}
|
||||
|
||||
// 校验用户当前状态(禁用账号不允许进入后台)
|
||||
var user models.User
|
||||
if err := database.DB.First(&user, claims.UserID).Error; err != nil || user.Status == 0 || !user.IsActive {
|
||||
if err := database.DB.First(&user, userID).Error; err != nil || user.Status == 0 || !user.IsActive {
|
||||
c.Redirect(http.StatusFound, "/admin/login")
|
||||
return
|
||||
}
|
||||
|
||||
+24
-30
@@ -103,6 +103,24 @@ func CreateComment(c *gin.Context) {
|
||||
c.JSON(http.StatusCreated, gin.H{"data": comment})
|
||||
}
|
||||
|
||||
// loadAndVerifyComment 加载评论并校验权限,成功返回评论,失败直接写响应并返回 nil
|
||||
func loadAndVerifyComment(c *gin.Context) *models.Comment {
|
||||
id := c.Param("id")
|
||||
|
||||
var comment models.Comment
|
||||
if err := database.DB.First(&comment, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "评论不存在"})
|
||||
return nil
|
||||
}
|
||||
|
||||
if !canManageComment(c, &comment) {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "无权管理该评论"})
|
||||
return nil
|
||||
}
|
||||
|
||||
return &comment
|
||||
}
|
||||
|
||||
// canManageComment 检查当前用户是否有权管理该评论(管理员始终可以,普通用户只能管理自己文章上的评论)
|
||||
func canManageComment(c *gin.Context, comment *models.Comment) bool {
|
||||
if isAdmin(c) {
|
||||
@@ -120,16 +138,8 @@ func canManageComment(c *gin.Context, comment *models.Comment) bool {
|
||||
|
||||
// 审核评论
|
||||
func ApproveComment(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
var comment models.Comment
|
||||
if err := database.DB.First(&comment, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "评论不存在"})
|
||||
return
|
||||
}
|
||||
|
||||
if !canManageComment(c, &comment) {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "无权管理该评论"})
|
||||
comment := loadAndVerifyComment(c)
|
||||
if comment == nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -144,16 +154,8 @@ func ApproveComment(c *gin.Context) {
|
||||
|
||||
// 标记为垃圾评论
|
||||
func MarkSpamComment(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
var comment models.Comment
|
||||
if err := database.DB.First(&comment, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "评论不存在"})
|
||||
return
|
||||
}
|
||||
|
||||
if !canManageComment(c, &comment) {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "无权管理该评论"})
|
||||
comment := loadAndVerifyComment(c)
|
||||
if comment == nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -168,16 +170,8 @@ func MarkSpamComment(c *gin.Context) {
|
||||
|
||||
// 删除评论
|
||||
func DeleteComment(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
|
||||
var comment models.Comment
|
||||
if err := database.DB.First(&comment, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "评论不存在"})
|
||||
return
|
||||
}
|
||||
|
||||
if !canManageComment(c, &comment) {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "无权管理该评论"})
|
||||
comment := loadAndVerifyComment(c)
|
||||
if comment == nil {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
+24
-19
@@ -72,6 +72,20 @@ func generateSlug(title string) string {
|
||||
return slug
|
||||
}
|
||||
|
||||
// findOrCreateTags 根据名称列表查找或创建标签
|
||||
func findOrCreateTags(tagNames []string) []models.Tag {
|
||||
var tags []models.Tag
|
||||
for _, tagName := range tagNames {
|
||||
var tag models.Tag
|
||||
database.DB.FirstOrCreate(&tag, models.Tag{
|
||||
Name: tagName,
|
||||
Slug: generateSlug(tagName),
|
||||
})
|
||||
tags = append(tags, tag)
|
||||
}
|
||||
return tags
|
||||
}
|
||||
|
||||
// 获取文章列表
|
||||
func GetPosts(c *gin.Context) {
|
||||
var query PostListQuery
|
||||
@@ -80,6 +94,14 @@ func GetPosts(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// 分页参数校验
|
||||
if query.Page < 1 {
|
||||
query.Page = 1
|
||||
}
|
||||
if query.PageSize < 1 || query.PageSize > 100 {
|
||||
query.PageSize = 10
|
||||
}
|
||||
|
||||
db := database.DB.Model(&models.Post{}).Preload("Category").Preload("Tags").Preload("Author")
|
||||
|
||||
// 管理员可查看全部;登录用户可看到自己的全部文章(含草稿);未登录只能看已发布
|
||||
@@ -191,16 +213,7 @@ func CreatePost(c *gin.Context) {
|
||||
|
||||
// 处理标签
|
||||
if len(req.Tags) > 0 {
|
||||
var tags []models.Tag
|
||||
for _, tagName := range req.Tags {
|
||||
var tag models.Tag
|
||||
database.DB.FirstOrCreate(&tag, models.Tag{
|
||||
Name: tagName,
|
||||
Slug: generateSlug(tagName),
|
||||
})
|
||||
tags = append(tags, tag)
|
||||
}
|
||||
post.Tags = tags
|
||||
post.Tags = findOrCreateTags(req.Tags)
|
||||
}
|
||||
|
||||
if err := database.DB.Create(&post).Error; err != nil {
|
||||
@@ -272,15 +285,7 @@ func UpdatePost(c *gin.Context) {
|
||||
|
||||
// 处理标签
|
||||
if len(req.Tags) > 0 {
|
||||
var tags []models.Tag
|
||||
for _, tagName := range req.Tags {
|
||||
var tag models.Tag
|
||||
database.DB.FirstOrCreate(&tag, models.Tag{
|
||||
Name: tagName,
|
||||
Slug: generateSlug(tagName),
|
||||
})
|
||||
tags = append(tags, tag)
|
||||
}
|
||||
tags := findOrCreateTags(req.Tags)
|
||||
if err := database.DB.Model(&post).Association("Tags").Replace(tags); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "更新文章标签失败"})
|
||||
return
|
||||
|
||||
+1
-5
@@ -4,7 +4,6 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"goblog/config"
|
||||
"goblog/database"
|
||||
"goblog/models"
|
||||
|
||||
@@ -21,10 +20,7 @@ const (
|
||||
|
||||
// GetSettings 获取站点基础设置(管理员)
|
||||
func GetSettings(c *gin.Context) {
|
||||
cfg := config.GetGlobalConfig()
|
||||
if cfg == nil {
|
||||
cfg = config.Load()
|
||||
}
|
||||
cfg := getConfig()
|
||||
|
||||
opts, err := models.GetOptionsByUser(database.DB, 0)
|
||||
if err != nil {
|
||||
|
||||
+10
-17
@@ -237,19 +237,19 @@ func GetThemeFiles(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
// resolveThemeFilePath 根据主题名和文件名解析实际文件路径
|
||||
func resolveThemeFilePath(themeName, filename string) string {
|
||||
if filename == themeName+".css" {
|
||||
return filepath.Join("static", "css", filename)
|
||||
}
|
||||
return filepath.Join("templates", themeName, filename)
|
||||
}
|
||||
|
||||
// 获取主题文件内容
|
||||
func GetThemeFile(c *gin.Context) {
|
||||
themeName := c.Param("theme")
|
||||
filename := c.Param("file")
|
||||
|
||||
var filePath string
|
||||
|
||||
// 判断是模板文件还是 CSS 文件
|
||||
if filename == themeName+".css" {
|
||||
filePath = filepath.Join("static", "css", filename)
|
||||
} else {
|
||||
filePath = filepath.Join("templates", themeName, filename)
|
||||
}
|
||||
filePath := resolveThemeFilePath(themeName, filename)
|
||||
|
||||
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
@@ -288,14 +288,7 @@ func SaveThemeFile(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
var filePath string
|
||||
|
||||
// 判断是模板文件还是 CSS 文件
|
||||
if filename == themeName+".css" {
|
||||
filePath = filepath.Join("static", "css", filename)
|
||||
} else {
|
||||
filePath = filepath.Join("templates", themeName, filename)
|
||||
}
|
||||
filePath := resolveThemeFilePath(themeName, filename)
|
||||
|
||||
// 保存文件
|
||||
if err := os.WriteFile(filePath, []byte(req.Content), 0600); err != nil {
|
||||
|
||||
+40
-91
@@ -1,7 +1,6 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"math"
|
||||
"net/http"
|
||||
"strconv"
|
||||
@@ -15,6 +14,34 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// getConfig 获取全局配置(启动时已初始化,直接返回)
|
||||
func getConfig() *config.Config {
|
||||
if cfg := config.GetGlobalConfig(); cfg != nil {
|
||||
return cfg
|
||||
}
|
||||
return config.Load()
|
||||
}
|
||||
|
||||
// loadSiteInfo 加载站点名称和描述(优先数据库,回退配置文件)
|
||||
func loadSiteInfo(cfg *config.Config) (siteName, siteDesc string) {
|
||||
siteName = cfg.App.Name
|
||||
siteDesc = cfg.App.Description
|
||||
if v, err := models.GetOptionValue(database.DB, OptionSiteName, 0); err == nil && v != "" {
|
||||
siteName = v
|
||||
}
|
||||
if v, err := models.GetOptionValue(database.DB, OptionSiteDesc, 0); err == nil && v != "" {
|
||||
siteDesc = v
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// loadPublishedPages 加载所有已发布页面(按排序)
|
||||
func loadPublishedPages() []models.Page {
|
||||
var pages []models.Page
|
||||
database.DB.Where("status = ?", "published").Order("`order` ASC").Find(&pages)
|
||||
return pages
|
||||
}
|
||||
|
||||
// loadSidebarData 加载侧边栏数据
|
||||
func loadSidebarData() (categories []models.Category, tags []models.Tag, postCount, categoryCount, tagCount int64) {
|
||||
database.DB.Order("name ASC").Find(&categories)
|
||||
@@ -58,10 +85,7 @@ type IndexData struct {
|
||||
}
|
||||
|
||||
func IndexView(c *gin.Context) {
|
||||
cfg := config.GetGlobalConfig()
|
||||
if cfg == nil {
|
||||
cfg = config.Load()
|
||||
}
|
||||
cfg := getConfig()
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
if page < 1 {
|
||||
page = 1
|
||||
@@ -91,39 +115,15 @@ func IndexView(c *gin.Context) {
|
||||
var total int64
|
||||
db.Count(&total)
|
||||
|
||||
// 调试日志
|
||||
slog.Info("首页查询", "total", total, "page", page, "categoryID", categoryID, "tagID", tagID)
|
||||
|
||||
var posts []models.Post
|
||||
offset := (page - 1) * pageSize
|
||||
db.Order("is_top DESC, published_at DESC, created_at DESC").
|
||||
Offset(offset).Limit(pageSize).Find(&posts)
|
||||
|
||||
// 调试日志
|
||||
slog.Info("查询结果", "postCount", len(posts))
|
||||
for i, p := range posts {
|
||||
slog.Info("文章", "index", i, "id", p.ID, "title", p.Title, "status", p.Status, "publishedAt", p.PublishedAt, "views", p.Views, "commentsCount", len(p.Comments))
|
||||
}
|
||||
|
||||
// 获取页面
|
||||
var pages []models.Page
|
||||
database.DB.Where("status = ?", "published").Order("`order` ASC").Find(&pages)
|
||||
|
||||
totalPages := int(math.Ceil(float64(total) / float64(pageSize)))
|
||||
|
||||
// 加载侧边栏数据
|
||||
categories, tags, postCount, categoryCount, tagCount := loadSidebarData()
|
||||
|
||||
// 从数据库读取主题设置,如果不存在则使用配置文件中的值
|
||||
siteName := cfg.App.Name
|
||||
siteDesc := cfg.App.Description
|
||||
|
||||
if v, err := models.GetOptionValue(database.DB, "site_name", 0); err == nil {
|
||||
siteName = v
|
||||
}
|
||||
if v, err := models.GetOptionValue(database.DB, "site_desc", 0); err == nil {
|
||||
siteDesc = v
|
||||
}
|
||||
siteName, siteDesc := loadSiteInfo(cfg)
|
||||
pages := loadPublishedPages()
|
||||
|
||||
data := IndexData{
|
||||
Title: "首页",
|
||||
@@ -176,10 +176,7 @@ type PostDetailData struct {
|
||||
}
|
||||
|
||||
func PostView(c *gin.Context) {
|
||||
cfg := config.GetGlobalConfig()
|
||||
if cfg == nil {
|
||||
cfg = config.Load()
|
||||
}
|
||||
cfg := getConfig()
|
||||
slug := c.Param("slug")
|
||||
|
||||
var post models.Post
|
||||
@@ -207,9 +204,7 @@ func PostView(c *gin.Context) {
|
||||
// 增加浏览量
|
||||
database.DB.Model(&post).UpdateColumn("views", gorm.Expr("views + 1"))
|
||||
|
||||
// 获取页面
|
||||
var pages []models.Page
|
||||
database.DB.Where("status = ?", "published").Order("`order` ASC").Find(&pages)
|
||||
pages := loadPublishedPages()
|
||||
|
||||
// 统计评论数
|
||||
var commentCount int64
|
||||
@@ -262,18 +257,7 @@ func PostView(c *gin.Context) {
|
||||
nextErr := database.DB.Where("status = ? AND published_at > ?", "published", post.PublishedAt).
|
||||
Order("published_at ASC").First(&nextPost).Error
|
||||
|
||||
// 从数据库读取站点设置
|
||||
siteName := cfg.App.Name
|
||||
siteDesc := cfg.App.Description
|
||||
|
||||
if v, err := models.GetOptionValue(database.DB, "site_name", 0); err == nil {
|
||||
siteName = v
|
||||
}
|
||||
if v, err := models.GetOptionValue(database.DB, "site_desc", 0); err == nil {
|
||||
siteDesc = v
|
||||
}
|
||||
|
||||
// 加载侧边栏数据
|
||||
siteName, siteDesc := loadSiteInfo(cfg)
|
||||
categories, tags, postCount, categoryCount, tagCount := loadSidebarData()
|
||||
|
||||
data := PostDetailData{
|
||||
@@ -331,10 +315,7 @@ type PageDetailData struct {
|
||||
}
|
||||
|
||||
func PageView(c *gin.Context) {
|
||||
cfg := config.GetGlobalConfig()
|
||||
if cfg == nil {
|
||||
cfg = config.Load()
|
||||
}
|
||||
cfg := getConfig()
|
||||
slug := c.Param("slug")
|
||||
|
||||
var page models.Page
|
||||
@@ -348,21 +329,8 @@ func PageView(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// 获取所有页面
|
||||
var pages []models.Page
|
||||
database.DB.Where("status = ?", "published").Order("`order` ASC").Find(&pages)
|
||||
|
||||
// 从数据库读取站点设置
|
||||
siteName := cfg.App.Name
|
||||
siteDesc := cfg.App.Description
|
||||
|
||||
if v, err := models.GetOptionValue(database.DB, "site_name", 0); err == nil {
|
||||
siteName = v
|
||||
}
|
||||
if v, err := models.GetOptionValue(database.DB, "site_desc", 0); err == nil {
|
||||
siteDesc = v
|
||||
}
|
||||
// 加载侧边栏数据
|
||||
pages := loadPublishedPages()
|
||||
siteName, siteDesc := loadSiteInfo(cfg)
|
||||
categories, tags, postCount, categoryCount, tagCount := loadSidebarData()
|
||||
|
||||
data := PageDetailData{
|
||||
@@ -419,10 +387,7 @@ func SubmitComment(c *gin.Context) {
|
||||
|
||||
// 搜索结果页
|
||||
func SearchView(c *gin.Context) {
|
||||
cfg := config.GetGlobalConfig()
|
||||
if cfg == nil {
|
||||
cfg = config.Load()
|
||||
}
|
||||
cfg := getConfig()
|
||||
|
||||
keyword := c.Query("keyword")
|
||||
if keyword == "" {
|
||||
@@ -449,26 +414,10 @@ func SearchView(c *gin.Context) {
|
||||
db.Order("published_at DESC").
|
||||
Offset(offset).Limit(pageSize).Find(&posts)
|
||||
|
||||
// 获取页面
|
||||
var pages []models.Page
|
||||
database.DB.Where("status = ?", "published").Order("`order` ASC").Find(&pages)
|
||||
|
||||
pages := loadPublishedPages()
|
||||
totalPages := int(math.Ceil(float64(total) / float64(pageSize)))
|
||||
|
||||
// 加载侧边栏数据
|
||||
categories, tags, postCount, categoryCount, tagCount := loadSidebarData()
|
||||
|
||||
// 从数据库读取主题设置
|
||||
siteName := cfg.App.Name
|
||||
siteDesc := cfg.App.Description
|
||||
|
||||
var option models.Option
|
||||
if err := database.DB.Where("name = ? AND user_id = ?", "site_name", 0).First(&option).Error; err == nil {
|
||||
siteName = option.Value
|
||||
}
|
||||
if err := database.DB.Where("name = ? AND user_id = ?", "site_desc", 0).First(&option).Error; err == nil {
|
||||
siteDesc = option.Value
|
||||
}
|
||||
siteName, siteDesc := loadSiteInfo(cfg)
|
||||
|
||||
data := IndexData{
|
||||
Title: "搜索: " + keyword,
|
||||
|
||||
@@ -55,7 +55,7 @@ func main() {
|
||||
routers.SetupTemplates(r, theme)
|
||||
|
||||
// 配置所有路由
|
||||
routers.SetupRoutes(r, cfg)
|
||||
routers.SetupRoutes(r)
|
||||
|
||||
// 启动服务器
|
||||
port := routers.GetServerPort(cfg.Server.Port)
|
||||
|
||||
@@ -201,12 +201,6 @@ func SetOptionValue(db *gorm.DB, name string, userID uint, value string) error {
|
||||
return db.Create(&option).Error
|
||||
}
|
||||
|
||||
// DeleteOption 删除配置项
|
||||
func DeleteOption(db *gorm.DB, name string, userID uint) error {
|
||||
result := db.Where("name = ? AND user_id = ?", name, userID).Delete(&Option{})
|
||||
return result.Error
|
||||
}
|
||||
|
||||
// GetOptionsByUser 获取用户的所有配置
|
||||
func GetOptionsByUser(db *gorm.DB, userID uint) (map[string]string, error) {
|
||||
var options []Option
|
||||
|
||||
+3
-3
@@ -11,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
// SetupRoutes 配置所有路由
|
||||
func SetupRoutes(r *gin.Engine, cfg interface{}) {
|
||||
func SetupRoutes(r *gin.Engine) {
|
||||
// 静态文件
|
||||
r.Static("/static", "./static")
|
||||
|
||||
@@ -19,8 +19,8 @@ func SetupRoutes(r *gin.Engine, cfg interface{}) {
|
||||
r.Static("/admin/static", "./static/admin")
|
||||
// 独立登录页
|
||||
r.GET("/admin/login", handlers.AdminLoginView)
|
||||
// 后台首页:服务端按登录角色用模板渲染菜单与页面框架
|
||||
r.GET("/admin", handlers.AdminView)
|
||||
// 后台首页:可选认证中间件注入用户信息,服务端按角色渲染菜单与页面框架
|
||||
r.GET("/admin", middleware.OptionalAuth(), handlers.AdminView)
|
||||
|
||||
// 前端页面路由(可选认证:侧边栏根据登录态显示系统菜单)
|
||||
r.GET("/", middleware.OptionalAuth(), handlers.IndexView)
|
||||
|
||||
Reference in New Issue
Block a user