专栏首页独行猫a的沉淀积累总结Golang--Go语言 五百行后台代码实现一简约的个人博客网站-TinyBlog

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

博客演示地址: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")
}

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 物联网MQTT协议报文解析(简单的c语音客户端实现)

    MQTT(Message Queuing Telemetry Transport),是一个物联网传输协议,它被设计用于轻量级的发布/订阅式消息传输,旨在为低带宽...

    特立独行的猫a
  • 嵌入式linux之go语言开发(十二)参数配置文件存储模块开发

    比如常用的可以用json文件,XML文件,或INI文件,YAML文件,properties文件等存储配置信息。

    特立独行的猫a
  • go语言微信公众号开发后台接口封装

    业余时间做了个有意思的小功能,每天早上7点准时给发天气预报,每晚8点发布一条英语说说,提醒自己不能忘记学习。

    特立独行的猫a
  • 数据库PostrageSQL-服务器配置设置参数

    有很多配置参数可以影响数据库系统的行为。本章的第一节中我们将描述一下如何与配置参数交互。 后续的小节将详细地讨论每一个参数。

    cwl_java
  • 运行网站出现Too many open files,CentOS 6.x和CentOS 7.x解决方法 Linux 面板

    奇梦
  • 运行网站出现Too many open files,CentOS 6.x和CentOS 7.x解决方法

    针对新手使用Appnode常见问题: 有没有安装完Appnode,配置好网站正常运行一段时间出现报错: Too many open files  出现以上报错可...

    奇梦
  • Java访问带有Kerberos认证的HBase

    stys35
  • fpm_stdio_prepare_pipes(), line 197: pipe() failed: Too many open files

    有太服务器上挂了几个采集站,采集进程比较多,设置了 limits.conf和 sysctl.conf 值为102400,php日志还是提示标题所示内容。

    用户1469148
  • 免费为你的域名申请SSL证书和配置教程

    我想,点进来的朋友,应该都知道SSL的重要性吧。这里就简单提一下,大型网站域名只有配置了SSL后,才会更加安全。

    大宇云的客户
  • dnsmasq安装使用和体验

    因为公司内部要搞邮件服务器,所以就需要在内部搭建一个dns服务器来实现域名解析,因为我不想搞得太复杂,所以就使用dnsmasq来做dns解析的服务器了,这个以前...

    bboysoul

扫码关注云+社区

领取腾讯云代金券