增加配置文件
This commit is contained in:
+29
-21
@@ -1,6 +1,7 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
@@ -9,7 +10,6 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
var jwtSecret = []byte("your-secret-key-change-in-production")
|
||||
@@ -54,16 +54,22 @@ func Login(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if user.Status == 0 {
|
||||
if user.Status == 0 || !user.IsActive {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "账号已被禁用"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.Password)); err != nil {
|
||||
// 使用 User 模型的 CheckPassword 方法
|
||||
if !user.CheckPassword(req.Password) {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "用户名或密码错误"})
|
||||
return
|
||||
}
|
||||
|
||||
// 记录登录信息
|
||||
if err := user.RecordLogin(database.DB); err != nil {
|
||||
slog.Warn("记录登录信息失败", "user_id", user.ID, "error", err)
|
||||
}
|
||||
|
||||
// 生成 JWT
|
||||
claims := Claims{
|
||||
UserID: user.ID,
|
||||
@@ -85,12 +91,14 @@ func Login(c *gin.Context) {
|
||||
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,
|
||||
"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,
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -118,20 +126,20 @@ func Register(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// 加密密码
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "密码加密失败"})
|
||||
return
|
||||
}
|
||||
|
||||
// 使用 User 模型的 SetPassword 方法
|
||||
user := models.User{
|
||||
Username: req.Username,
|
||||
Password: string(hashedPassword),
|
||||
Nickname: req.Nickname,
|
||||
Email: req.Email,
|
||||
Role: "user",
|
||||
Status: 1,
|
||||
IsActive: true,
|
||||
}
|
||||
|
||||
// 加密密码
|
||||
if err := user.SetPassword(req.Password); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "密码加密失败"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := database.DB.Create(&user).Error; err != nil {
|
||||
@@ -221,18 +229,18 @@ func ChangePassword(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.OldPassword)); err != nil {
|
||||
// 使用 User 模型的 CheckPassword 方法
|
||||
if !user.CheckPassword(req.OldPassword) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "原密码错误"})
|
||||
return
|
||||
}
|
||||
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
// 使用 User 模型的 SetPassword 方法
|
||||
if err := user.SetPassword(req.NewPassword); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "密码加密失败"})
|
||||
return
|
||||
}
|
||||
|
||||
user.Password = string(hashedPassword)
|
||||
if err := database.DB.Save(&user).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "修改密码失败"})
|
||||
return
|
||||
|
||||
@@ -0,0 +1,435 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"goblog/database"
|
||||
"goblog/models"
|
||||
"goblog/utils"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// 获取可用主题列表
|
||||
func GetThemes(c *gin.Context) {
|
||||
themes := []string{}
|
||||
|
||||
// 读取 templates 目录
|
||||
entries, err := os.ReadDir("templates")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "无法读取主题目录",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() {
|
||||
// 检查是否是有效主题(包含 base.html)
|
||||
basePath := filepath.Join("templates", entry.Name(), "base.html")
|
||||
if _, err := os.Stat(basePath); err == nil {
|
||||
themes = append(themes, entry.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"themes": themes,
|
||||
})
|
||||
}
|
||||
|
||||
// 获取当前主题
|
||||
func GetCurrentTheme(c *gin.Context) {
|
||||
currentTheme := "default"
|
||||
|
||||
// 使用新的 Option 查询方法
|
||||
theme, err := models.GetOptionValue(database.DB, "theme", 0)
|
||||
if err == nil && theme != "" {
|
||||
currentTheme = theme
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"theme": currentTheme,
|
||||
})
|
||||
}
|
||||
|
||||
// 切换主题
|
||||
func SwitchTheme(c *gin.Context) {
|
||||
type Request struct {
|
||||
Theme string `json:"theme" binding:"required"`
|
||||
}
|
||||
|
||||
var req Request
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "请提供主题名称",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 验证主题是否存在
|
||||
themePath := filepath.Join("templates", req.Theme)
|
||||
if _, err := os.Stat(themePath); os.IsNotExist(err) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "主题不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 验证主题是否有效
|
||||
basePath := filepath.Join(themePath, "base.html")
|
||||
if _, err := os.Stat(basePath); os.IsNotExist(err) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "主题文件不完整",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 更新数据库中的主题设置
|
||||
err := models.SetOptionValue(database.DB, "theme", 0, req.Theme)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "保存主题设置失败"})
|
||||
return
|
||||
}
|
||||
|
||||
// 重新加载模板(立即生效)
|
||||
renderer := utils.GetGlobalRenderer()
|
||||
if renderer != nil {
|
||||
renderer.Reload(req.Theme)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "主题切换成功",
|
||||
"theme": req.Theme,
|
||||
})
|
||||
}
|
||||
|
||||
// 创建新主题
|
||||
func CreateTheme(c *gin.Context) {
|
||||
type Request struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
Template string `json:"template"`
|
||||
CreateCSS bool `json:"create_css"`
|
||||
}
|
||||
|
||||
var req Request
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "请提供主题名称",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 验证主题名称(只能包含字母、数字和下划线)
|
||||
validName := true
|
||||
for _, ch := range req.Name {
|
||||
if !((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch == '_') {
|
||||
validName = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if !validName || req.Name == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "主题名称只能包含字母、数字和下划线",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 检查主题是否已存在
|
||||
themePath := filepath.Join("templates", req.Name)
|
||||
if _, err := os.Stat(themePath); err == nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "主题已存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 创建主题目录
|
||||
if err := os.MkdirAll(themePath, 0755); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "创建主题目录失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 如果指定了模板,复制模板文件
|
||||
if req.Template != "" && req.Template != "blank" {
|
||||
templateSrc := filepath.Join("templates", req.Template)
|
||||
if _, err := os.Stat(templateSrc); err == nil {
|
||||
// 复制模板文件
|
||||
files := []string{"base.html", "index.html", "post.html", "page.html"}
|
||||
for _, file := range files {
|
||||
srcFile := filepath.Join(templateSrc, file)
|
||||
dstFile := filepath.Join(themePath, file)
|
||||
if data, err := os.ReadFile(srcFile); err == nil {
|
||||
os.WriteFile(dstFile, data, 0644)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 创建空白模板
|
||||
baseHTML := `<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{.Title}} - {{.SiteName}}</title>
|
||||
<meta name="description" content="{{.Description}}">
|
||||
<link rel="icon" href="/static/images/favicon.ico" type="image/x-icon">
|
||||
<link rel="stylesheet" href="/static/css/{{.Name}}.css">
|
||||
</head>
|
||||
<body>
|
||||
<header class="header">
|
||||
<div class="container">
|
||||
<h1 class="site-title">
|
||||
<a href="/">{{.SiteName}}</a>
|
||||
</h1>
|
||||
<p class="site-desc">{{.SiteDesc}}</p>
|
||||
<nav class="main-nav">
|
||||
<a href="/">首页</a>
|
||||
{{range .Pages}}
|
||||
<a href="/page/{{.Slug}}">{{.Title}}</a>
|
||||
{{end}}
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="main container">
|
||||
{{template "content" .}}
|
||||
</main>
|
||||
|
||||
<footer class="footer">
|
||||
<div class="container">
|
||||
<p>© {{.Year}} {{.SiteName}}. Powered by GoBlog.</p>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
os.WriteFile(filepath.Join(themePath, "base.html"), []byte(baseHTML), 0644)
|
||||
os.WriteFile(filepath.Join(themePath, "index.html"), []byte(`{{define "content"}}
|
||||
<div class="post-list">
|
||||
<h2>欢迎来到我的博客</h2>
|
||||
<p>这是一个新主题</p>
|
||||
</div>
|
||||
{{end}}
|
||||
`), 0644)
|
||||
os.WriteFile(filepath.Join(themePath, "post.html"), []byte(`{{define "content"}}
|
||||
<article class="post-detail">
|
||||
<h1>{{.Post.Title}}</h1>
|
||||
<div class="post-body">
|
||||
{{.Post.Content | html}}
|
||||
</div>
|
||||
</article>
|
||||
{{end}}
|
||||
`), 0644)
|
||||
os.WriteFile(filepath.Join(themePath, "page.html"), []byte(`{{define "content"}}
|
||||
<article class="page-detail">
|
||||
<h1>{{.Page.Title}}</h1>
|
||||
<div class="page-body">
|
||||
{{.Page.Content | html}}
|
||||
</div>
|
||||
</article>
|
||||
{{end}}
|
||||
`), 0644)
|
||||
}
|
||||
|
||||
// 创建 CSS 文件
|
||||
if req.CreateCSS {
|
||||
cssPath := filepath.Join("static", "css", req.Name+".css")
|
||||
os.MkdirAll(filepath.Dir(cssPath), 0755)
|
||||
|
||||
cssContent := `/* ` + req.Name + ` Theme */
|
||||
/* 在此添加你的样式 */
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.header {
|
||||
background: #fff;
|
||||
padding: 20px 0;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.site-title {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.site-title a {
|
||||
color: #333;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.site-desc {
|
||||
color: #666;
|
||||
margin: 10px 0 0;
|
||||
}
|
||||
|
||||
.main-nav {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.main-nav a {
|
||||
margin-right: 15px;
|
||||
color: #666;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.main {
|
||||
padding: 30px 0;
|
||||
min-height: 500px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
background: #f5f5f5;
|
||||
padding: 20px 0;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
}
|
||||
`
|
||||
os.WriteFile(cssPath, []byte(cssContent), 0644)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "主题创建成功",
|
||||
"theme": req.Name,
|
||||
})
|
||||
}
|
||||
|
||||
// 获取主题文件列表
|
||||
func GetThemeFiles(c *gin.Context) {
|
||||
themeName := c.Param("theme")
|
||||
|
||||
themePath := filepath.Join("templates", themeName)
|
||||
if _, err := os.Stat(themePath); os.IsNotExist(err) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "主题不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
files := []string{}
|
||||
|
||||
// 读取模板文件
|
||||
entries, err := os.ReadDir(themePath)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "读取文件列表失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
if !entry.IsDir() {
|
||||
ext := filepath.Ext(entry.Name())
|
||||
if ext == ".html" || ext == ".css" || ext == ".js" {
|
||||
files = append(files, entry.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否有 CSS 文件
|
||||
cssPath := filepath.Join("static", "css", themeName+".css")
|
||||
if _, err := os.Stat(cssPath); err == nil {
|
||||
files = append(files, themeName+".css")
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"files": files,
|
||||
})
|
||||
}
|
||||
|
||||
// 获取主题文件内容
|
||||
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)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "文件不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
content, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "读取文件失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"content": string(content),
|
||||
})
|
||||
}
|
||||
|
||||
// 保存主题文件
|
||||
func SaveThemeFile(c *gin.Context) {
|
||||
themeName := c.Param("theme")
|
||||
filename := c.Param("file")
|
||||
|
||||
type Request struct {
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
var req Request
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "请提供文件内容",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var filePath string
|
||||
|
||||
// 判断是模板文件还是 CSS 文件
|
||||
if filename == themeName+".css" {
|
||||
filePath = filepath.Join("static", "css", filename)
|
||||
} else {
|
||||
filePath = filepath.Join("templates", themeName, filename)
|
||||
}
|
||||
|
||||
// 保存文件
|
||||
if err := os.WriteFile(filePath, []byte(req.Content), 0644); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "保存文件失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 如果是模板文件且是当前使用的主题,重新加载
|
||||
if filename != themeName+".css" {
|
||||
currentTheme, err := models.GetOptionValue(database.DB, "theme", 0)
|
||||
if err == nil && currentTheme == themeName {
|
||||
renderer := utils.GetGlobalRenderer()
|
||||
if renderer != nil {
|
||||
renderer.Reload(themeName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "文件保存成功",
|
||||
})
|
||||
}
|
||||
+193
-10
@@ -2,6 +2,7 @@ package handlers
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"log/slog"
|
||||
"math"
|
||||
"net/http"
|
||||
"strconv"
|
||||
@@ -15,6 +16,16 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
func templateFuncs() template.FuncMap {
|
||||
return template.FuncMap{
|
||||
"add": func(a, b int) int {
|
||||
@@ -40,10 +51,19 @@ type IndexData struct {
|
||||
CurrentPage int
|
||||
TotalPages int
|
||||
Year int
|
||||
// Sidebar 数据
|
||||
Categories []models.Category
|
||||
Tags []models.Tag
|
||||
PostCount int64
|
||||
CategoryCount int64
|
||||
TagCount int64
|
||||
}
|
||||
|
||||
func IndexView(c *gin.Context) {
|
||||
cfg := config.Load()
|
||||
cfg := config.GetGlobalConfig()
|
||||
if cfg == nil {
|
||||
cfg = config.Load()
|
||||
}
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
if page < 1 {
|
||||
page = 1
|
||||
@@ -67,27 +87,57 @@ 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)
|
||||
}
|
||||
|
||||
// 获取页面
|
||||
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
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
data := IndexData{
|
||||
Title: "首页",
|
||||
SiteName: cfg.App.Name,
|
||||
SiteDesc: cfg.App.Description,
|
||||
Description: cfg.App.Description,
|
||||
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,
|
||||
}
|
||||
|
||||
c.HTML(http.StatusOK, "index", data)
|
||||
@@ -106,10 +156,19 @@ type PostDetailData struct {
|
||||
Year int
|
||||
PrevPost *models.Post // 上一篇
|
||||
NextPost *models.Post // 下一篇
|
||||
// Sidebar 数据
|
||||
Categories []models.Category
|
||||
Tags []models.Tag
|
||||
PostCount int64
|
||||
CategoryCount int64
|
||||
TagCount int64
|
||||
}
|
||||
|
||||
func PostView(c *gin.Context) {
|
||||
cfg := config.Load()
|
||||
cfg := config.GetGlobalConfig()
|
||||
if cfg == nil {
|
||||
cfg = config.Load()
|
||||
}
|
||||
slug := c.Param("slug")
|
||||
|
||||
var post models.Post
|
||||
@@ -152,16 +211,37 @@ 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
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 加载侧边栏数据
|
||||
categories, tags, postCount, categoryCount, tagCount := loadSidebarData()
|
||||
|
||||
data := PostDetailData{
|
||||
Title: post.Title,
|
||||
SiteName: cfg.App.Name,
|
||||
SiteDesc: cfg.App.Description,
|
||||
SiteName: siteName,
|
||||
SiteDesc: siteDesc,
|
||||
Description: post.Summary,
|
||||
Post: post,
|
||||
Pages: pages,
|
||||
Comments: post.Comments,
|
||||
CommentCount: commentCount,
|
||||
Year: time.Now().Year(),
|
||||
// Sidebar 数据
|
||||
Categories: categories,
|
||||
Tags: tags,
|
||||
PostCount: postCount,
|
||||
CategoryCount: categoryCount,
|
||||
TagCount: tagCount,
|
||||
}
|
||||
|
||||
// 只有当查询成功时才赋值
|
||||
@@ -184,10 +264,19 @@ type PageDetailData struct {
|
||||
Page models.Page
|
||||
Pages []models.Page
|
||||
Year int
|
||||
// Sidebar 数据
|
||||
Categories []models.Category
|
||||
Tags []models.Tag
|
||||
PostCount int64
|
||||
CategoryCount int64
|
||||
TagCount int64
|
||||
}
|
||||
|
||||
func PageView(c *gin.Context) {
|
||||
cfg := config.Load()
|
||||
cfg := config.GetGlobalConfig()
|
||||
if cfg == nil {
|
||||
cfg = config.Load()
|
||||
}
|
||||
slug := c.Param("slug")
|
||||
|
||||
var page models.Page
|
||||
@@ -205,14 +294,35 @@ func PageView(c *gin.Context) {
|
||||
var pages []models.Page
|
||||
database.DB.Where("status = ?", "published").Order("`order` ASC").Find(&pages)
|
||||
|
||||
// 从数据库读取站点设置
|
||||
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
|
||||
}
|
||||
|
||||
// 加载侧边栏数据
|
||||
categories, tags, postCount, categoryCount, tagCount := loadSidebarData()
|
||||
|
||||
data := PageDetailData{
|
||||
Title: page.Title,
|
||||
SiteName: cfg.App.Name,
|
||||
SiteDesc: cfg.App.Description,
|
||||
SiteName: siteName,
|
||||
SiteDesc: siteDesc,
|
||||
Description: "",
|
||||
Page: page,
|
||||
Pages: pages,
|
||||
Year: time.Now().Year(),
|
||||
// Sidebar 数据
|
||||
Categories: categories,
|
||||
Tags: tags,
|
||||
PostCount: postCount,
|
||||
CategoryCount: categoryCount,
|
||||
TagCount: tagCount,
|
||||
}
|
||||
|
||||
c.HTML(http.StatusOK, "page", data)
|
||||
@@ -249,3 +359,76 @@ func SubmitComment(c *gin.Context) {
|
||||
|
||||
c.Redirect(http.StatusFound, "/post/"+post.Slug)
|
||||
}
|
||||
|
||||
// 搜索结果页
|
||||
func SearchView(c *gin.Context) {
|
||||
cfg := config.GetGlobalConfig()
|
||||
if cfg == nil {
|
||||
cfg = config.Load()
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
// 获取页面
|
||||
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
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
c.HTML(http.StatusOK, "index", data)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user