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 = "[email protected]"
	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")
}

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章