Files
go_typecho/handlers/view.go
T

442 lines
12 KiB
Go

package handlers
import (
"math"
"net/http"
"strconv"
"time"
"goblog/config"
"goblog/database"
"goblog/models"
"github.com/gin-gonic/gin"
"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)
database.DB.Order("name ASC").Find(&tags)
database.DB.Model(&models.Post{}).Where("status = ?", "published").Count(&postCount)
database.DB.Model(&models.Category{}).Count(&categoryCount)
database.DB.Model(&models.Tag{}).Count(&tagCount)
return
}
// loginInfo 从上下文提取登录态信息(供侧边栏系统菜单区分已登录/未登录)
func loginInfo(c *gin.Context) (isLoggedIn bool, loginName string) {
if _, ok := c.Get("userID"); !ok {
return false, ""
}
name, _ := c.Get("username")
loginName, _ = name.(string)
return true, loginName
}
// 首页
type IndexData struct {
Title string
SiteName string
SiteDesc string
Description string
Posts []models.Post
Pages []models.Page
CurrentPage int
TotalPages int
Year int
// Sidebar 数据
Categories []models.Category
Tags []models.Tag
PostCount int64
CategoryCount int64
TagCount int64
// 登录态(侧边栏系统菜单用)
IsLoggedIn bool
LoginName string
}
func IndexView(c *gin.Context) {
cfg := getConfig()
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
if page < 1 {
page = 1
}
pageSize := 10
categoryID, _ := strconv.Atoi(c.Query("category"))
tagID, _ := strconv.Atoi(c.Query("tag"))
db := database.DB.Model(&models.Post{}).Preload("Category").Preload("Tags").Preload("Author").
Where("status = ?", "published")
userId, exists := c.Get("userID")
if exists {
db = db.Or("author_id = ?", userId)
}
if categoryID > 0 {
db = db.Where("category_id = ?", categoryID)
}
if tagID > 0 {
db = db.Joins("JOIN post_tags ON post_tags.post_id = posts.id").
Where("post_tags.tag_id = ?", tagID)
}
var total int64
db.Count(&total)
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)
totalPages := int(math.Ceil(float64(total) / float64(pageSize)))
categories, tags, postCount, categoryCount, tagCount := loadSidebarData()
siteName, siteDesc := loadSiteInfo(cfg)
pages := loadPublishedPages()
data := IndexData{
Title: "首页",
SiteName: siteName,
SiteDesc: siteDesc,
Description: siteDesc,
Posts: posts,
Pages: pages,
CurrentPage: page,
TotalPages: totalPages,
Year: time.Now().Year(),
// Sidebar 数据
Categories: categories,
Tags: tags,
PostCount: postCount,
CategoryCount: categoryCount,
TagCount: tagCount,
}
data.IsLoggedIn, data.LoginName = loginInfo(c)
c.HTML(http.StatusOK, "index", data)
}
// 文章详情页
type PostDetailData struct {
Title string
SiteName string
SiteDesc string
Description string
Post models.Post
Pages []models.Page
Comments []models.Comment
CommentCount int64
Year int
PrevPost *models.Post // 上一篇
NextPost *models.Post // 下一篇
// 评论展示设置与分页
ShowComments bool
CommentPage int
CommentTotalPages int
// Sidebar 数据
Categories []models.Category
Tags []models.Tag
PostCount int64
CategoryCount int64
TagCount int64
// 登录态(侧边栏系统菜单用)
IsLoggedIn bool
LoginName string
}
func PostView(c *gin.Context) {
cfg := getConfig()
slug := c.Param("slug")
var post models.Post
query := database.DB.Preload("Category").Preload("Tags").Preload("Author").
Where("status = ?", "published").
Session(&gorm.Session{}) // 创建可安全复用的查询会话,避免两次查询条件叠加
err := query.Where("slug = ?", slug).First(&post).Error
// slug 未命中时,若参数为纯数字则回退按文章 ID 查询(兼容 /post/17 形式的链接)
if err != nil {
if id, convErr := strconv.Atoi(slug); convErr == nil {
err = query.Where("id = ?", id).First(&post).Error
}
}
if err != nil {
c.HTML(http.StatusNotFound, "index", gin.H{
"Title": "404 - 页面不存在",
"SiteName": cfg.App.Name,
"Year": time.Now().Year(),
})
return
}
// 增加浏览量
database.DB.Model(&post).UpdateColumn("views", gorm.Expr("views + 1"))
pages := loadPublishedPages()
// 统计评论数
var commentCount int64
database.DB.Model(&models.Comment{}).Where("post_id = ? AND status = ?", post.ID, "approved").Count(&commentCount)
// 读取评论展示设置
showComments := true
if v, optErr := models.GetOptionValue(database.DB, OptionShowComments, 0); optErr == nil {
showComments = v != "0"
}
commentPageSize := 10
if v, optErr := models.GetOptionValue(database.DB, OptionCommentPageSize, 0); optErr == nil {
if n, convErr := strconv.Atoi(v); convErr == nil && n > 0 {
commentPageSize = n
}
}
// 顶级评论分页查询(回复随父评论一起展示,不单独分页)
commentPage, _ := strconv.Atoi(c.DefaultQuery("cpage", "1"))
if commentPage < 1 {
commentPage = 1
}
var comments []models.Comment
commentTotalPages := 0
if showComments {
var topCount int64
database.DB.Model(&models.Comment{}).
Where("post_id = ? AND status = ? AND parent_id IS NULL", post.ID, "approved").
Count(&topCount)
commentTotalPages = int(math.Ceil(float64(topCount) / float64(commentPageSize)))
if commentTotalPages > 0 && commentPage > commentTotalPages {
commentPage = commentTotalPages
}
database.DB.Where("post_id = ? AND status = ? AND parent_id IS NULL", post.ID, "approved").
Preload("Children", func(db *gorm.DB) *gorm.DB {
return db.Where("status = ?", "approved")
}).
Order("created_at ASC").
Offset((commentPage - 1) * commentPageSize).Limit(commentPageSize).
Find(&comments)
}
// 获取上一篇(发布时间更早的文章)
var prevPost models.Post
prevErr := database.DB.Where("status = ? AND published_at < ?", "published", post.PublishedAt).
Order("published_at DESC").First(&prevPost).Error
// 获取下一篇(发布时间更晚的文章)
var nextPost models.Post
nextErr := database.DB.Where("status = ? AND published_at > ?", "published", post.PublishedAt).
Order("published_at ASC").First(&nextPost).Error
siteName, siteDesc := loadSiteInfo(cfg)
categories, tags, postCount, categoryCount, tagCount := loadSidebarData()
data := PostDetailData{
Title: post.Title,
SiteName: siteName,
SiteDesc: siteDesc,
Description: post.Summary,
Post: post,
Pages: pages,
Comments: comments,
CommentCount: commentCount,
Year: time.Now().Year(),
// 评论展示设置与分页
ShowComments: showComments,
CommentPage: commentPage,
CommentTotalPages: commentTotalPages,
// Sidebar 数据
Categories: categories,
Tags: tags,
PostCount: postCount,
CategoryCount: categoryCount,
TagCount: tagCount,
}
data.IsLoggedIn, data.LoginName = loginInfo(c)
// 只有当查询成功时才赋值
if prevErr == nil {
data.PrevPost = &prevPost
}
if nextErr == nil {
data.NextPost = &nextPost
}
c.HTML(http.StatusOK, "post", data)
}
// 独立页面
type PageDetailData struct {
Title string
SiteName string
SiteDesc string
Description string
Page models.Page
Pages []models.Page
Year int
// Sidebar 数据
Categories []models.Category
Tags []models.Tag
PostCount int64
CategoryCount int64
TagCount int64
// 登录态(侧边栏系统菜单用)
IsLoggedIn bool
LoginName string
}
func PageView(c *gin.Context) {
cfg := getConfig()
slug := c.Param("slug")
var page models.Page
err := database.DB.Where("slug = ? AND status = ?", slug, "published").First(&page).Error
if err != nil {
c.HTML(http.StatusNotFound, "index", gin.H{
"Title": "404 - 页面不存在",
"SiteName": cfg.App.Name,
"Year": time.Now().Year(),
})
return
}
pages := loadPublishedPages()
siteName, siteDesc := loadSiteInfo(cfg)
categories, tags, postCount, categoryCount, tagCount := loadSidebarData()
data := PageDetailData{
Title: page.Title,
SiteName: siteName,
SiteDesc: siteDesc,
Description: "",
Page: page,
Pages: pages,
Year: time.Now().Year(),
// Sidebar 数据
Categories: categories,
Tags: tags,
PostCount: postCount,
CategoryCount: categoryCount,
TagCount: tagCount,
}
data.IsLoggedIn, data.LoginName = loginInfo(c)
c.HTML(http.StatusOK, "page", data)
}
// 提交评论(表单提交)
func SubmitComment(c *gin.Context) {
postID, _ := strconv.Atoi(c.PostForm("post_id"))
author := c.PostForm("author")
email := c.PostForm("email")
website := c.PostForm("website")
content := c.PostForm("content")
// 检查文章是否存在
var post models.Post
if err := database.DB.First(&post, postID).Error; err != nil {
c.String(http.StatusBadRequest, "文章不存在")
return
}
comment := models.Comment{
PostID: uint(postID),
Author: author,
Email: email,
Website: website,
Content: content,
Status: "pending",
}
if err := database.DB.Create(&comment).Error; err != nil {
c.String(http.StatusInternalServerError, "提交失败")
return
}
c.Redirect(http.StatusFound, "/post/"+post.Slug)
}
// 搜索结果页
func SearchView(c *gin.Context) {
cfg := getConfig()
keyword := c.Query("keyword")
if keyword == "" {
c.Redirect(http.StatusFound, "/")
return
}
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
if page < 1 {
page = 1
}
pageSize := 10
db := database.DB.Model(&models.Post{}).Preload("Category").Preload("Tags").Preload("Author").
Where("status = ?", "published").
Where("title LIKE ? OR content LIKE ? OR summary LIKE ?",
"%"+keyword+"%", "%"+keyword+"%", "%"+keyword+"%")
var total int64
db.Count(&total)
var posts []models.Post
offset := (page - 1) * pageSize
db.Order("published_at DESC").
Offset(offset).Limit(pageSize).Find(&posts)
pages := loadPublishedPages()
totalPages := int(math.Ceil(float64(total) / float64(pageSize)))
categories, tags, postCount, categoryCount, tagCount := loadSidebarData()
siteName, siteDesc := loadSiteInfo(cfg)
data := IndexData{
Title: "搜索: " + keyword,
SiteName: siteName,
SiteDesc: siteDesc,
Description: "搜索关键词: " + keyword,
Posts: posts,
Pages: pages,
CurrentPage: page,
TotalPages: totalPages,
Year: time.Now().Year(),
Categories: categories,
Tags: tags,
PostCount: postCount,
CategoryCount: categoryCount,
TagCount: tagCount,
}
data.IsLoggedIn, data.LoginName = loginInfo(c)
c.HTML(http.StatusOK, "index", data)
}