(一)项目框架
采用gin+gorm框架 用 gin自带的模板来创建前后端页面
Gin 是一个用于构建 HTTP 服务器的 web 框架。 它提供了一个简单易用的 API,用于处理 HTTP 请求和响应、路由、中间件和其他常见的 web 应用程序所需的功能。 它以其高性能和简约为特点,提供了轻量级和灵活的解决方案来构建 web 服务器。 Gorm 是 Go 的一个 ORM(对象关系映射)库。 它提供了一个简单易用的 API,用于与数据库交互、处理数据库迁移和执行常见的数据库操作,如查询、插入、更新和删除记录。 它支持多种数据库后端,包括 MySQL、PostgreSQL、SQLite 等。 总而言之, Gin 是用于处理 HTTP 请求和响应、路由、中间件和其他与网络相关的东西的 web 框架,而 Gorm 则是用于与数据库交互并执行常见数据库操作的 ORM 库。
(二)初始化项目
(1)编程工具我们采用vscode
新建blog文件夹 用cmd打开 在cmd中 输出
code .
即可进入
(2)初始化项目
在控制台输入:
go mod init Blog
go mod init用法是在当前目录项目下初始化mod123,用于管理项目的依赖模块。go mod init后面可以跟项目名
(三)项目架构
(1)创建静态资源文件夹
创建静态资源文件夹assets,在改文件夹中再创建两个文件夹backend和forntend,分别保存前端静态资源和后端静态资源
(2)创建项目模板文件夹
创建保存项目末班的文件夹templates,在其下面创建两个文件夹backend和forntend分别保存前端模板和后端模板
(3)创建公共类文件夹
创建文件夹common,在其下面创建config,global,initialize,middleware和utils
(4)创建控制器文件夹
创建控制器文件夹controller
(5)创建服务文件夹servic
创建服务器文件夹service
(四) 准备数据库和模型
(1)创建一个数据库 名称为blog_db
create database blog_db
(2)创建一个表,该表为博客分类频道
CREATE TABLE `channel` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`title` varchar(75) NOT NULL,
`slug` varchar(100) NOT NULL,
`content` text ,
`status` int(5) NOT NULL,
`weight` int(11) NOT NULL,
PRIMARY KEY (`id`)
);
(3) 创建对应模型
在models文件夹下面,创建名称为channel.go的文件
内容如下:
package models
// 数据库,用户数据映射模型
type Channel struct {
Id uint64 `form:"id" gorm:"primaryKey"`
Title string `form:"title" gorm:"title"`
Slug string `form:"slug" gorm:"slug"`
Content string `form:"content" gorm:"content"`
Status int `form:"status" gorm:"status"`
Weight int `form:"weight" gorm:"weight"`
}
(五)创建项目配置
(1)安装读取配置文件包viper
github.com/spf13/viper
(2)在根目录下创建配置文件config.yaml
# 启动端口号
server:
post: 8001
# MySQL数据源配置
mysql:
username: root # 修改为自己数据库的用户名
password: 123456 # 修改为自己数据库的密码
url: tcp(localhost:3306)/blog_db?charset=utf8&parseTime=True&loc=Local
(3)创建配置类
在common下面的config下面创建config.go
package config
// 组合全部配置模型
type Config struct {
Server Server `mapstructure:"server"`
Mysql Mysql `mapstructure:"mysql"`
}
// 服务启动端口号配置
type Server struct {
Post string `mapstructure:"post"`
}
// MySQL数据源配置
type Mysql struct {
Username string `mapstructure:"username"`
Password string `mapstructure:"password"`
Url string `mapstructure:"url"`
}
(六)创建数据库连接
(1)在common 下的initialize目录下面创建db.go文件
package initialize
import (
"fmt"
"time"
"blog/common/global"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/schema"
)
// Mysql 配置MySQl数据库
func Mysql() {
m := global.Config.Mysql
var dsn = fmt.Sprintf("%s:%s@%s", m.Username, m.Password, m.Url)
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
NamingStrategy: schema.NamingStrategy{SingularTable: true},
})
if err != nil {
fmt.Printf("mysql error: %s", err)
return
}
sqlDb, err := db.DB()
if err != nil {
fmt.Printf("mysql error: %s", err)
}
sqlDb.SetMaxIdleConns(10)
sqlDb.SetMaxOpenConns(100)
sqlDb.SetConnMaxLifetime(time.Hour)
global.Db = db
}
(七)创建全局变量
(1)在common下面的global目录下面创建global.go
package global
import (
"blog/common/config"
"gorm.io/gorm"
)
var (
Config config.Config
Db *gorm.DB
)
(八)创建操作频道service
(1)在service目录下创建channel.go文件
package service
import (
"blog/common/global"
"blog/models"
)
type ChannelService struct {
}
// 添加
func (u *ChannelService) AddChannel(channel models.Channel) int64 {
return global.Db.Table("channel").Create(&channel).RowsAffected
}
// 删除
func (u *ChannelService) DelChannel(id int) int64 {
return global.Db.Delete(&models.Channel{}, id).RowsAffected
}
// 修改
func (u *ChannelService) UpdateChannel(channel models.Channel) int64 {
return global.Db.Updates(&channel).RowsAffected
}
// get
func (u *ChannelService) GetChannel(id int) models.Channel {
var channel models.Channel
global.Db.First(&channel, id)
return channel
}
// get channel list
func (u *ChannelService) GetChannelList() []models.Channel {
channelList := make([]models.Channel, 0)
global.Db.Find(&channelList)
return channelList
}
(九)创建操作博客频道controller
(1)安装模板安装包
go get github.com/foolin/gin-template
(2)在controller目录下创建channel.go文件
package controller
import (
"blog/models"
"blog/service"
"fmt"
"net/http"
"strconv"
gintemplate "github.com/foolin/gin-template"
"github.com/gin-gonic/gin"
)
var channel service.ChannelService
// channel list
func ListChannel(c *gin.Context) {
c2 := channel.GetChannelList()
gintemplate.HTML(c, http.StatusOK, "channel/list", gin.H{"clist": c2})
}
// view channel
func ViewChannel(c *gin.Context) {
sid, r := c.GetQuery("id")
var chann models.Channel
if r {
id, _ := strconv.Atoi(sid)
chann = channel.GetChannel(id)
}
gintemplate.HTML(c, http.StatusOK, "channel/view", gin.H{"channel": chann})
}
// delete channel
func DeleteChannel(c *gin.Context) {
sid, _ := c.GetQuery("id")
id, _ := strconv.Atoi(sid)
channel.DelChannel(id)
c.Redirect(http.StatusFound, "/admin/channel/list")
}
// 添加或者更新
func SaveChannel(c *gin.Context) {
var chann models.Channel
if err := c.ShouldBind(&chann); err != nil {
fmt.Printf("err: %v\n", err)
return
}
chann.Status, _ = strconv.Atoi(c.PostForm("status"))
id, _ := c.GetPostForm("id")
if id != "0" {
channel.UpdateChannel(chann)
} else {
channel.AddChannel(chann)
}
c.Redirect(http.StatusFound, "/admin/channel/list")
}
(十)准备静态资源和模板文件
(1)下载gin自带的前端框架和资源并替换原来的文件
链接: https://pan.baidu.com/s/1JJx8OEbE5GqEbuJtKFdzqQ?pwd=wasd 提取码: wasd
(十一)创建index信息传输文件
(1)在controller文件夹下创建index.go
package controller
import (
"net/http"
"blog/service"
gintemplate "github.com/foolin/gin-template"
"github.com/gin-gonic/gin"
)
var indexChannelService service.ChannelService
// 后台首页
func AdminIndex(c *gin.Context) {
gintemplate.HTML(c, http.StatusOK, "index", nil)
}
// 前台首页
func Index(c *gin.Context) {
channels := indexChannelService.GetChannelList()
gintemplate.HTML(c, http.StatusOK, "index", gin.H{"clist": channels})
}
(十二)创建路由文件
在common/initialize 文件下创建router.go文件
package initialize
import (
"fmt"
"blog/common/global"
"blog/controller"
gintemplate "github.com/foolin/gin-template"
"github.com/gin-gonic/gin"
)
func Router() {
engine := gin.Default()
// 静态资源请求映射
engine.Static("/assets", "./assets")
engine.HTMLRender = gintemplate.New(gintemplate.TemplateConfig{
Root: "templates/frontend",
Extension: ".html",
Master: "layouts/master",
DisableCache: true,
})
// 前台
engine.GET("/", controller.Index)
mw := gintemplate.NewMiddleware(gintemplate.TemplateConfig{
Root: "templates/backend",
Extension: ".html",
Master: "layouts/master",
DisableCache: true,
})
// 后台管理员前端接口
web := engine.Group("/admin", mw)
{
// index
web.GET("/", controller.AdminIndex)
}
{
// 用户登录API
web.GET("/channel/list", controller.ListChannel)
web.GET("/channel/view", controller.ViewChannel)
web.POST("/channel/save", controller.SaveChannel)
web.GET("/channel/del", controller.DeleteChannel)
}
// 启动、监听端口
post := fmt.Sprintf(":%s", global.Config.Server.Post)
if err := engine.Run(post); err != nil {
fmt.Printf("server start error: %s", err)
}
}
(十三)创建启动类main.go
(1)在根目录下创建main.go
package main
import (
"blog/common/initialize"
)
func main() {
print("hello")
initialize.LoadConfig()
initialize.Mysql()
initialize.Router()
}
(十四)检验频道前端
(1)启动main.go 检验前端成果 输入端口号 localhost:8001
(十五)准备发布博客post sql表
(1)代码如下:
CREATE TABLE `post` (
`id` bigint NOT NULL AUTO_INCREMENT, # 主键
`created_at` datetime DEFAULT NULL, # 创建时间
`updated_at` datetime DEFAULT NULL, # 更新时间
`deleted_at` datetime DEFAULT NULL, # 删除时间
`title` varchar(64) DEFAULT NULL, # 标题
`thumbnail` varchar(128) DEFAULT NULL, #缩略图
`summary` varchar(140) DEFAULT NULL, # 摘要
`content` longtext DEFAULT "", # 内容
`author_id` bigint DEFAULT NULL, #作者id
`channel_id` int DEFAULT NULL, # 频道id
`comments` int DEFAULT 0, # 评论数
`favors` int DEFAULT 0, # 收藏数
`featured` int DEFAULT 0, # 特征
`status` int DEFAULT 0, # 状态
`tags` varchar(64) DEFAULT NULL, # 标签
`views` int DEFAULT 0, # 浏览数
`weight` int DEFAULT 0, # 权重
`url` varchar(500) DEFAULT "", # 转载url
PRIMARY KEY (`id`)
);
(十六)创建博客post对应模型
(1)在models文件夹下面创建post.go文件,内容如下:
package models
import "gorm.io/gorm"
// 数据库,博客数据映射模型
type Post struct {
gorm.Model
Title string `gorm:"slug"`
Thumbnail string `gorm:"thumbnail"`
Summary string `gorm:"summary"`
Content string `gorm:"content"`
AuthorId int `gorm:"author_id"`
ChannelId int `gorm:"channel_id"`
Comments int `gorm:"comments"`
Favors int `gorm:"favors"`
Featured int `gorm:"featured"`
Status int `gorm:"status"`
Tags string `gorm:"tags"`
Views int `gorm:"views"`
Weight int `gorm:"weight"`
Url string `gorm:"url"`
}
(十七)创建博客post service
(1)在service 目录下面创建一个post.go文件 来实现对post的curd
package service
import (
"blog/common/global"
"blog/models"
)
type PostService struct {
}
// 添加
func (p *PostService) AddPost(post models.Post) int64 {
return global.Db.Table("post").Create(&post).RowsAffected
}
// 删除
func (p *PostService) DelPost(id int) int64 {
return global.Db.Delete(&models.Post{}, id).RowsAffected
}
// 修改
func (p *PostService) UpdatePost(post models.Post) int64 {
return global.Db.Updates(&post).RowsAffected
}
// get
func (p *PostService) GetPost(id int) models.Post {
var post models.Post
global.Db.First(&post, id)
return post
}
// get post list
func (p *PostService) GetPostList() []models.Post {
postList := make([]models.Post, 0)
global.Db.Find(&postList)
return postList
}
(十八)创建博客post对应控制器
(1)在controller 目录下面,创建post.go文件
package controller
import (
"blog/common/stripmd"
"blog/models"
"blog/service"
"fmt"
"html/template"
"net/http"
"os"
"path/filepath"
"strconv"
"github.com/gomarkdown/markdown"
"github.com/gomarkdown/markdown/parser"
gintemplate "github.com/foolin/gin-template"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/spf13/cast"
)
var postService service.PostService
var channelService service.ChannelService
// post list
func ListPost(c *gin.Context) {
plist := postService.GetPostList()
gintemplate.HTML(c, http.StatusOK, "post/list", gin.H{"plist": plist})
}
// view post
func ViewPost(c *gin.Context) {
sid, r := c.GetQuery("id")
var post models.Post
if r {
id, _ := strconv.Atoi(sid)
post = postService.GetPost(id)
}
clist := channelService.GetChannelList()
gintemplate.HTML(c, http.StatusOK, "post/view", gin.H{"post": post, "clist": clist})
}
// delete post
func DeletePost(c *gin.Context) {
sid, _ := c.GetQuery("id")
id, _ := strconv.Atoi(sid)
postService.DelPost(id)
c.Redirect(http.StatusFound, "/admin/post/list")
}
// post detail 前端
func PostDetail(c *gin.Context) {
sid := c.Param("id")
id := cast.ToInt((sid))
post := postService.GetPost(id)
extensions := parser.CommonExtensions | parser.AutoHeadingIDs
parser := parser.NewWithExtensions(extensions)
md := []byte(post.Content)
md = markdown.NormalizeNewlines(md)
html := markdown.ToHTML(md, parser, nil)
gintemplate.HTML(c, http.StatusOK, "/post/detail", gin.H{"post": post, "content": template.HTML(html)})
}
// 上传封面
func UploadThumbnails(c *gin.Context) {
file, err := c.FormFile("file")
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{
"message": "没有文件",
})
return
}
extension := filepath.Ext(file.Filename)
newFileName := uuid.New().String() + extension
// The file is received, so let's save it
pwd, _ := os.Getwd() // 项目根路径
filePath := fmt.Sprintf("%s/static/thumbnails/%s", pwd, newFileName)
relativeFilePath := fmt.Sprintf("/static/thumbnails/%s", newFileName)
if err := c.SaveUploadedFile(file, filePath); err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
"message": "不能保存文件",
})
fmt.Printf("err: %v\n", err)
return
}
c.String(http.StatusOK, relativeFilePath)
}
// 添加或者更新
func SavePost(c *gin.Context) {
var post models.Post
// id := c.PostForm("id")
title := c.PostForm("title")
thumbnail := c.PostForm("thumbnail")
tags := c.PostForm("tags")
content := c.PostForm("content")
channelId := c.PostForm("channelId")
// 摘要,去掉markdown格式
summary := stripmd.Strip(content)
l := len(summary)
if l >= 200 {
summary = summary[0:200]
} else {
summary = summary[0:l]
}
post.Title = title
post.Thumbnail = thumbnail
post.Tags = tags
post.Content = content
post.Summary = summary
post.AuthorId = 1 // 实现注册登录后,动态获得
post.ChannelId = cast.ToInt(channelId)
if err := c.ShouldBind(&post); err != nil {
fmt.Printf("err: %v\n", err)
return
}
id := cast.ToInt(c.PostForm("id"))
if id != 0 {
postService.UpdatePost(post)
} else {
postService.AddPost(post)
}
c.Redirect(http.StatusFound, "/admin/post/list")
}
(十九)重置路由
(1)在router.go文件中加入post相关的路由
package initialize
import (
"fmt"
"net/http"
"blog/common/global"
"blog/controller"
gintemplate "github.com/foolin/gin-template"
"github.com/gin-gonic/gin"
)
func Router() {
engine := gin.Default()
// 静态资源请求映射
engine.Static("/assets", "./assets")
engine.StaticFS("/static", http.Dir("./static"))
engine.HTMLRender = gintemplate.New(gintemplate.TemplateConfig{
Root: "templates/frontend",
Extension: ".html",
Master: "layouts/master",
DisableCache: true,
})
// 前台
engine.GET("/", controller.Index)
engine.GET("/post/:id", controller.PostDetail)
mw := gintemplate.NewMiddleware(gintemplate.TemplateConfig{
Root: "templates/backend",
Extension: ".html",
Master: "layouts/master",
DisableCache: true,
})
// 后台管理员前端接口
web := engine.Group("/admin", mw)
{
// index
web.GET("/", controller.AdminIndex)
}
{
// channel
web.GET("/channel/list", controller.ListChannel)
web.GET("/channel/view", controller.ViewChannel)
web.POST("/channel/save", controller.SaveChannel)
web.GET("/channel/del", controller.DeleteChannel)
}
{
// post
web.GET("/post/list", controller.ListPost)
web.GET("/post/view", controller.ViewPost)
web.POST("/post/save", controller.SavePost)
web.GET("/post/del", controller.DeletePost)
web.POST("/post/upload", controller.UploadThumbnails)
}
// 启动、监听端口
post := fmt.Sprintf(":%s", global.Config.Server.Post)
if err := engine.Run(post); err != nil {
fmt.Printf("server start error: %s", err)
}
}
(二十) 检验
后台管理界面
频道操作
添加频道
博客列表
添加博客
实现页面