ai优化,删除未使用代码,提取重复代码。

This commit is contained in:
2026-08-12 11:12:23 +08:00
parent 043bf8773b
commit 60869ec214
10 changed files with 108 additions and 201 deletions
+1 -16
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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,
+1 -1
View File
@@ -55,7 +55,7 @@ func main() {
routers.SetupTemplates(r, theme)
// 配置所有路由
routers.SetupRoutes(r, cfg)
routers.SetupRoutes(r)
// 启动服务器
port := routers.GetServerPort(cfg.Server.Port)
-6
View File
@@ -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
View File
@@ -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)