handlers区分backend和frontend,需要确认。
This commit is contained in:
@@ -0,0 +1,435 @@
|
||||
package frontend
|
||||
|
||||
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, models.OptionSiteName, 0); err == nil && v != "" {
|
||||
siteName = v
|
||||
}
|
||||
if v, err := models.GetOptionValue(database.DB, models.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")
|
||||
|
||||
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, models.OptionShowComments, 0); optErr == nil {
|
||||
showComments = v != "0"
|
||||
}
|
||||
commentPageSize := 10
|
||||
if v, optErr := models.GetOptionValue(database.DB, models.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)
|
||||
}
|
||||
Reference in New Issue
Block a user