首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Golang--Go语言 五百行后台代码实现一简约的个人博客网站-TinyBlog

Golang--Go语言 五百行后台代码实现一简约的个人博客网站-TinyBlog

作者头像
杨永贞
发布2020-08-04 11:00:15
9280
发布2020-08-04 11:00:15
举报

博客演示地址:http://121.36.253.86/http://yangqq.xyz/,统计了下后台代码只有415行。

已被OsChina开源社区收录,地址在https://www.oschina.net/p/tinybg

当然,一个css或js文件都不可能这么短,这里仅指的是后台代码。

这得益于go语言的强大和设计思路的精巧,以及封装的go语言存储模块的简单好用。

三天晚上熬夜时间能够快速的实现,得益于站在巨人的肩膀上选择合适的利器为我所用,和晚上集中精力做一件事的效率。也是因为兴趣才使得能够坚持下去。

后台使用了Golang+ Gin web框架。

前端使用了流行的markdown-it(MarkDown渲染),highlight.js(语法高亮)和mermaid.js(画流程图、时序图等的js库)组件。

留言评论功能,界面爬取自网络大神阮一峰的个人博客界面样式,改了过来。

还获得了阮一峰的科技爱好者周刊(第 109 期https://mp.weixin.qq.com/s/0GHTm6hToNzPTtQMcEZalw)推荐。

如果你也想拥有一个属于自己的个性博客,这种尝试将大幅降低准入门槛,让你看到实现一个个人博客网站是多么的简单。其它的又是建库建表的,或是需要登录管理后台管理的,我还是觉得不够简单好用。写篇文章就要登录后台临时发挥编辑一遍吗?而这种思路写博客就是在电脑上写好md格式的文章后往目录丢写好的md文件,一样达到同样的目的。唯一需要熟悉下mardown的写法和遵循一定的格式。后续计划跟微信公众号结合,发布文章就是往聊天窗口丢写好的md文件即可。配合电脑上的markdown写作神器软件Typora,岂不美哉?我想把精力花在自己认为更有创新,更有意义的事情上来。

功能也不弱,支持文章的分页展示,文章中表格,图片和代码语法高亮,文章分类,按访问量统计,按时间和按点击量排序,展示最新文章,最热文章,文章留言评论,最新评论等功能。

整个后台代码就一个main.go 。

运行部署直接go run main.go 或者执行go build 后,执行./main就跑起来了。不用建库,也不用建表,部署超级简单,直接运行起来就行了。

比较行数没有意义。其实用哪种语言实现也都行,短时间达到目的就行。

意义不在于比较,而在于做事。做自己喜欢的事。

博客网站已经有很多,其实做没啥意义。我的目的一是出于兴趣爱好,一直以来都想有一个属于自己的博客梦。先后用过PHP,python实现过,但都对其都不太满意。二是主要是用来练兵,后续计划跟公众号功能打通。往公众号聊天窗口丢 md格式的文件就是发布文章,并接收前台留言推送。练兵的意义不在于立刻投入战斗,而在于需要用到相关资源的时候能够快速战斗,而非被卡脖子。华为,就是这么有远见。这次他遇到了卡脖子,但他有技术储备,他不怕。

在扯远点儿,人,活着的意义是什么?我觉得是做你喜欢的事,做有意义的事,无悔的事。

俞敏洪说过一句话,名利和地位不是你一味的追求就能获得的,而是你坚持用心的做一件事,自然而然而来的。他就是如此,乔布斯亦是如此。之前有个访谈节目问乔布斯,为什么要创办苹果?为了钱吗?他笑了,说如果因为钱他根本没办法坚持,甚至投入精力。人,习惯于懒惰。人应该支配习惯,而决不能让习惯支配人。人的一生应该怎样的度过才有意义?《钢铁是怎样炼成的》中的保尔柯察金说:人,最宝贵的是生命,生命对于每个人只有一次。这仅有的一次生命应该怎样度过呢?每当回首往事的时候,不会因为虚度年华而悔恨,也不因碌碌无为而羞愧。

网上有人说,"俞敏洪这种公知还是算了,很多公知都有点问题,就是老想语不惊人死不休,但实际上放之四海而皆准的公理大家都懂,所以就开始搞些偷换概念的歪理,一听有点道理,一推敲就不是味儿。而且说乔布斯访谈的,估计乔布斯传都没看过,乔布斯二十出头就是亿万身家,当然不为了赚钱了,但我们是普通人。坚持做一件事真的很难,没钱就没法坚持了。",但是话虽如此,如果没有兴趣爱好或情怀投入里面,真的能坚持吗,能做好吗?真的能挣钱吗?

不是说要去崇拜谁,迷信谁,盲从谁。做好自己就行了。

毕竟耳朵在自己身上,对了听,不对扔。仅此而已,一笑了之。

有人说我的兴趣爱好就是打牌,打游戏。但是打游戏也有以此为职业的,这告诉我们最好是能稍微的把兴趣爱好同职业结合起来,实现个人的价值和对社会的贡献。虽然每个人对人生的意义理解不尽相同,但没准这样充实和做喜欢做的事,用心做事,不管结果如何,本身就是一种意义所在。这个社会每个人都少不了柴米油盐,少有谁能像诗仙李白那样的飘柔朗逸。但是先用心把事情做好,多少投入点儿兴趣和情怀,和你追求财富并不矛盾,并且是告诉我们一个道理,切莫因急功近利而变得浮躁沉不下心来认真做事,那样只会适得其反,欲速则不达。

开源地址:

https://gitee.com/yyz116/tinybg

https://github.com/yangyongzhen/tinybg

这里注意下上图的前六行,必须遵循这样的格式。分别是 文章标题,日期,简介,首部的图片展示,文章分类和作者,后面的才是文章内容。

附后台代码实现:

package main

import (
	"crypto/md5"
	"encoding/hex"
	"fmt"
	"goblog/conf"
	"html/template"
	"io/ioutil"
	"net/http"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
)

type Post struct {
	ID      string //文章唯一ID 取 filename的md5
	Title   string
	Date    string
	Summary string
	Body    string
	File    string
	ImgFile string // 文章前的图片展示
	Item    string //分类
	Author  string //作者

	Cmts    []conf.Comment //评论
	CmtCnt  int            //评论数量
	VistCnt int            //浏览量
}

// Notice 跳转提示
type Notice struct {
	Mess    string
	IsSucc  bool
	TimeOut int
	Href    string
}

var articlesMap map[string]string /*创建集合 */

// NewArticles 最新文章按日期排序
type NewArticles []conf.Articles

// HotArticles 热门文章按访问量排序
type HotArticles []conf.Articles

//  NewArts  最新文章
var NewArts NewArticles

// HotArts 热门文章
var HotArts HotArticles

// NewPosts 总的文章,按时间排过序的
var NewPosts NewArticles

// NewCmts 最新评论
var NewCmts []conf.Comment

//文章排序的实现
//文章排序
//Len()
func (a NewArticles) Len() int {
	return len(a)
}
//Less():将有低到高排序
func (a NewArticles) Less(i, j int) bool {
	//fmt.Println(a[i].Date)
	//fmt.Println(a[j].Date)
	return a[i].Date > a[j].Date
}
//Swap()
func (a NewArticles) Swap(i, j int) {
	a[i], a[j] = a[j], a[i]
}
//Len()
func (a HotArticles) Len() int {
	return len(a)
}

//Less():将有低到高排序
func (a HotArticles) Less(i, j int) bool {
	//fmt.Println(a[i].Date)
	//fmt.Println(a[j].Date)
	return a[i].VistCnt > a[j].VistCnt
}

//Swap()
func (a HotArticles) Swap(i, j int) {
	a[i], a[j] = a[j], a[i]
}

// md5Str 计算MD5值
func md5Str(str string) string {
	h := md5.New()
	h.Write([]byte(str))
	return hex.EncodeToString(h.Sum(nil))
}

// RefreshData 后台异步更新数据
func RefreshData() {
	//全局的最新评论,只显示最新的三条
	curcount := len(conf.Cmt.Comts) //总的评论数量
	NewCmts = conf.Cmt.Comts
	if curcount >= 3 {
		NewCmts = conf.Cmt.Comts[(curcount - 3):curcount] //最新的3条评论
	}
	//post := conf.Art.ArticlesMap[conf.Item.Items[id]]
	NewPosts = NewArticles{}
	HotArts = HotArticles{}
	for key, value := range conf.Art.ArticlesMap {
		fmt.Println(key)
		for _, value1 := range value {
			NewPosts = append(NewPosts, value1)
			HotArts = append(HotArts, value1)
		}
	}
	sort.Sort(NewPosts) //日期排序
	sort.Sort(HotArts)  //访问量排序
	//fmt.Println("IS Sorted?", sort.IsSorted(post))
	NewArts = NewPosts
	num := len(NewPosts)
	if num > 9 {
		NewArts = NewPosts[0:9]
		HotArts = HotArts[0:9]
	}
	conf.Art.Save()
	conf.Stat.Save()
}

func handleIndex(c *gin.Context) {
	fmt.Printf("%#v\n", NewPosts)
	spage := c.DefaultQuery("page", "1")
	fmt.Printf("cur page:%s\n", spage)
	page, _ := strconv.Atoi(spage)
	nums := len(NewPosts)
	allpage := nums / 5
	if nums%5 != 0 {
		allpage = nums/5 + 1
	}
	fmt.Printf("all page num:%d\n", allpage)
	posts := NewPosts
	if (page * 5) < nums {
		posts = NewPosts[(page-1)*5 : page*5]
	} else {
		posts = NewPosts[(page-1)*5 : nums]
	}
	tabs := make([]int, allpage+2) //分页表
	if (page - 1) == 0 {
		tabs[0] = 1
	} else {
		tabs[0] = page - 1
	}
	for i := 1; i <= allpage; i++ {
		tabs[i] = i
	}
	if page+1 <= allpage {
		tabs[allpage+1] = page + 1
	} else {
		tabs[allpage+1] = 1
	}
	fmt.Printf("tabs:%#v\n", tabs)
	conf.Stat.ToCnt++
	ip := c.ClientIP()
	fmt.Printf("client ip:%s\n", ip)
	conf.Stat.IPs = append(conf.Stat.IPs, ip)
	c.HTML(http.StatusOK, "index.html", gin.H{"post": posts, "items": conf.Item.Items, "about": conf.Abt, "newcmts": NewCmts, "newart": NewArts, "hotart": HotArts, "vistcnt": conf.Stat.ToCnt, "curpage": page, "tabs": tabs})
}

// strTrip 去除字符串的空格和换行
func strTrip(src string) string {
	str := strings.Replace(src, " ", "", -1)
	str = strings.Replace(str, "\r", "", -1)
	str = strings.Replace(str, "\n", "", -1)
	return str
}

func getPosts() []Post {
	a := []Post{}
	files, _ := filepath.Glob("posts/*")
	for i, f := range files {
		fmt.Println(i)
		fmt.Println(f)

		file := strings.Replace(f, "posts\\", "", -1)
		fmt.Println(file)
		file = strings.Replace(file, ".md", "", -1)
		fileread, _ := ioutil.ReadFile(f)
		lines := strings.Split(string(fileread), "\n")
		title := string(lines[0])
		date := strTrip(string(lines[1]))
		summary := string(lines[2])
		imgfile := string(lines[3])
		item := strTrip(string(lines[4]))
		author := string(lines[5])

		imgfile = strTrip(imgfile)
		body := ""
		//body := strings.Join(lines[4:len(lines)], "\n")
		fmt.Println(imgfile)
		id := md5Str(file)
		articlesMap[id] = f
		itemcount := len(conf.Item.Items)
		//查找分类看是否已存在
		if itemcount == 0 {
			conf.Item.Items = append(conf.Item.Items, item)
		} else {
			k := 0
			for k = 0; k < itemcount; k++ {
				if conf.Item.Items[k] == item {
					break
				}
			}
			if k >= itemcount {
				//分类之前未存在,添加分类
				conf.Item.Items = append(conf.Item.Items, item)
				itemcount = len(conf.Item.Items)
			}
		}

		for j := 0; j < itemcount; j++ {
			if conf.Item.Items[j] == item {
				art := conf.Articles{id, item, title, date, summary, file, imgfile, author, 0, 0}
				if conf.Art.ArticlesMap[item] == nil {
					conf.Art.ArticlesMap[item] = make(map[string]conf.Articles)
				}
				_, exists := conf.Art.ArticlesMap[item][id]
				if !exists {
					conf.Art.ArticlesMap[item][id] = art
				}
				break
			}
		}

		a = append(a, Post{id, title, date, summary, body, file, imgfile, item, author, nil, conf.Art.ArticlesMap[item][id].CmtCnt, conf.Art.ArticlesMap[item][id].VistCnt})
	}
	conf.Art.Save()
	conf.Item.Save()
	fmt.Printf("%#v\n", conf.Art.ArticlesMap)
	fmt.Printf("%#v\n", articlesMap)
	return a
}

func handleArticles(c *gin.Context) {
	id := c.Param("id")
	file := articlesMap[id]
	fmt.Println(id)
	fmt.Println(file)
	fileread, _ := ioutil.ReadFile(file)
	lines := strings.Split(string(fileread), "\n")
	title := string(lines[0])
	date := string(lines[1])
	summary := string(lines[2])
	imgfile := string(lines[3])
	item := strTrip(string(lines[4]))
	author := string(lines[5])
	imgfile = strTrip(imgfile)
	body := strings.Join(lines[5:len(lines)], "\n")
	//fmt.Println(body)
	//fmt.Printf("%#v\n", conf.Cmt.Comts)
	cmts := []conf.Comment{}
	count := 0 //评论数量
	for i, v := range conf.Cmt.Comts {
		//fmt.Printf("%#v\n", c)
		if v.ID == id {
			cmts = append(cmts, conf.Cmt.Comts[i])
			count++
		}
	}
	//文章浏览量++
	art := conf.Art.ArticlesMap[item][id]
	art.VistCnt++ //浏览数量加一
	conf.Art.ArticlesMap[item][id] = art

	go RefreshData()

	p := Post{id, title, date, summary, body, file, imgfile, item, author, cmts, conf.Art.ArticlesMap[item][id].CmtCnt, conf.Art.ArticlesMap[item][id].VistCnt}
	c.HTML(http.StatusOK, "article.html", gin.H{"post": p, "items": conf.Item.Items, "cmtcounts": count, "newcmts": NewCmts, "newart": NewArts, "hotart": HotArts, "vistcnt": conf.Stat.ToCnt})
}

func handleItems(c *gin.Context) {
	sid := c.Param("id")
	id, _ := strconv.Atoi(sid)
	all := conf.Art.ArticlesMap[conf.Item.Items[id]]
	fmt.Printf("%#v\n", all)
	posts := []conf.Articles{}
	// 遍历 hash
	for _, value := range all {
		posts = append(posts, value)
	}
	//支持分页
	spage := c.DefaultQuery("page", "1")
	fmt.Printf("cur page:%s\n", spage)
	page, _ := strconv.Atoi(spage)
	nums := len(posts)
	allpage := nums / 5
	if nums%5 != 0 {
		allpage = nums/5 + 1
	}
	fmt.Printf("all page num:%d\n", allpage)
	curposts := posts
	if (page * 5) < nums {
		curposts = posts[(page-1)*5 : page*5]
	} else {
		curposts = posts[(page-1)*5 : nums]
	}
	tabs := make([]int, allpage+2) //分页表
	if (page - 1) == 0 {
		tabs[0] = 1
	} else {
		tabs[0] = page - 1
	}
	for i := 1; i <= allpage; i++ {
		tabs[i] = i
	}
	if page+1 <= allpage {
		tabs[allpage+1] = page + 1
	} else {
		tabs[allpage+1] = 1
	}
	fmt.Printf("tabs:%#v\n", tabs)
	c.HTML(http.StatusOK, "items.html", gin.H{"post": curposts, "items": conf.Item.Items, "newcmts": NewCmts, "newart": NewArts, "hotart": HotArts, "vistcnt": conf.Stat.ToCnt, "curitem": id, "curpage": page, "tabs": tabs})
}

func handlePostComment(c *gin.Context) {

	sid := c.PostForm("id")    //文章ID
	item := c.PostForm("item") //文章分类
	item = strTrip(item)
	title := c.PostForm("title")   //文章标题
	text := c.PostForm("text")     //评论内容
	author := c.PostForm("author") //评论者
	email := c.PostForm("email")   //邮箱
	url := c.PostForm("url")       //评论者网址
	time := time.Now().Format("2006-01-02 15:04:05")

	fmt.Println(sid, item, text, author, email, url, time)

	href := "/article/" + sid
	notice := Notice{"提交成功", true, 3, href}
	art := conf.Art.ArticlesMap[item][sid]
	art.CmtCnt++ //评论数量加一
	conf.Art.ArticlesMap[item][sid] = art
	//fmt.Printf("%#v\n", conf.Art.ArticlesMap[item][sid])
	fmt.Println(conf.Art.ArticlesMap[item][sid].CmtCnt)
	//fmt.Printf("%#v\n", conf.Art.ArticlesMap[item])
	cmt := conf.Comment{sid, item, title, author, email, text, time, url}
	fmt.Printf("%#v\n", cmt)
	conf.Cmt.Comts = append(conf.Cmt.Comts, cmt)
	conf.Cmt.Save()
	//conf.Art.Save()

	go RefreshData()

	c.HTML(http.StatusOK, "success.html", gin.H{"notice": notice})
}

// Add ...模板里使用加
func Add(a, b int) int {
	return a + b
}

// Dec ...模板里使用减
func Dec(a, b int) int {
	return a - b
}
func main() {
	router := gin.Default()
	//增加几个模板自定义函数Add Dec
	router.SetFuncMap(template.FuncMap{
		"add": Add,
		"dec": Dec,
	})

	//关于
	conf.Abt.Name = "一米阳光"
	conf.Abt.Jobs = "嵌入式 linux | Android | go web"
	conf.Abt.WX = "yongzhen1111"
	conf.Abt.QQ = "534117529"
	conf.Abt.Email = "534117529@qq.com"
	conf.Abt.Save()
	//分类
	// conf.Item.Items = append(conf.Item.Items, "随笔")
	// fmt.Printf("%d\n", len(conf.Item.Items))
	//加载分类
	conf.Item.Load()

	//加载评论
	conf.Cmt.Load()

	//加载文章
	conf.Art.Load()
	//加载统计信息
	conf.Stat.Load()

	articlesMap = make(map[string]string)
	articleIndex := getPosts()
	fmt.Printf("%#v\n", articleIndex)

	//更新文章排序和浏览量
	RefreshData()
	//静态文件
	router.Static("/assets", "./static")
	//渲染html页面
	router.LoadHTMLGlob("views/*")
	router.GET("/", handleIndex)
	router.GET("/article/:id", handleArticles)
	router.GET("/items/:id", handleItems)
	router.POST("/comment", handlePostComment)
	//运行的端口
	router.Run(":8000")
}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-05-20 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档