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