赞
踩
博客演示地址:http://114.115.170.245:8081/,http://yangqq.xyz/,统计了下后台代码只有415行。
已被OsChina开源社区收录,地址在TinyBg首页、文档和下载 - 极小的个人博客 - OSCHINA - 中文开源技术交流社区
当然,一个css或js文件都不可能这么短,这里仅指的是后台代码。
这得益于go语言的强大和设计思路的精巧,以及封装的go语言存储模块的简单好用。
三天晚上熬夜时间能够快速的实现,得益于站在巨人的肩膀上选择合适的利器为我所用,和晚上集中精力做一件事的效率。也是因为兴趣才使得能够坚持下去。
后台使用了Golang+ Gin web框架。
前端使用了流行的markdown-it(MarkDown渲染),highlight.js(语法高亮)和mermaid.js(画流程图、时序图等的js库)组件。
留言评论功能,界面爬取自网络大神阮一峰的个人博客界面样式,改了过来。
还获得了阮一峰的科技爱好者周刊(第 109 期科技爱好者周刊(第 109 期):播客的价值)推荐。
如果你也想拥有一个属于自己的个性博客,这种尝试将大幅降低准入门槛,让你看到实现一个个人博客网站是多么的简单。其它的又是建库建表的,或是需要登录管理后台管理的,我还是觉得不够简单好用。写篇文章就要登录后台临时发挥编辑一遍吗?而这种思路写博客就是在电脑上写好md格式的文章后往目录丢写好的md文件,一样达到同样的目的。唯一需要熟悉下mardown的写法和遵循一定的格式。后续计划跟微信公众号结合,发布文章就是往聊天窗口丢写好的md文件即可。配合电脑上的markdown写作神器软件Typora,岂不美哉?我想把精力花在自己认为更有创新,更有意义的事情上来。
功能也不弱,支持文章的分页展示,文章中表格,图片和代码语法高亮,文章分类,按访问量统计,按时间和按点击量排序,展示最新文章,最热文章,文章留言评论,最新评论等功能。
整个后台代码就一个main.go 。
运行部署直接go run main.go 或者执行go build 后,执行./main就跑起来了。不用建库,也不用建表,部署超级简单,直接运行起来就行了。
比较行数没有意义。其实用哪种语言实现也都行,短时间达到目的就行。
意义不在于比较,而在于做事。做自己喜欢的事。
博客网站已经有很多,其实做没啥意义。我的目的一是出于兴趣爱好,一直以来都想有一个属于自己的博客梦。先后用过PHP,python实现过,但都对其都不太满意。二是主要是用来练兵,后续计划跟公众号功能打通。往公众号聊天窗口丢 md格式的文件就是发布文章,并接收前台留言推送。练兵的意义不在于立刻投入战斗,而在于需要用到相关资源的时候能够快速战斗,而非被卡脖子。华为,就是这么有远见。这次他遇到了卡脖子,但他有技术储备,他不怕。
在扯远点儿,人,活着的意义是什么?我觉得是做你喜欢的事,做有意义的事,无悔的事。
俞敏洪说过一句话,名利和地位不是你一味的追求就能获得的,而是你坚持用心的做一件事,自然而然而来的。他就是如此,乔布斯亦是如此。之前有个访谈节目问乔布斯,为什么要创办苹果?为了钱吗?他笑了,说如果因为钱他根本没办法坚持,甚至投入精力。人,习惯于懒惰。人应该支配习惯,而决不能让习惯支配人。人的一生应该怎样的度过才有意义?《钢铁是怎样炼成的》中的保尔柯察金说:人,最宝贵的是生命,生命对于每个人只有一次。这仅有的一次生命应该怎样度过呢?每当回首往事的时候,不会因为虚度年华而悔恨,也不因碌碌无为而羞愧。
网上有人说,"俞敏洪这种公知还是算了,很多公知都有点问题,就是老想语不惊人死不休,但实际上放之四海而皆准的公理大家都懂,所以就开始搞些偷换概念的歪理,一听有点道理,一推敲就不是味儿。而且说乔布斯访谈的,估计乔布斯传都没看过,乔布斯二十出头就是亿万身家,当然不为了赚钱了,但我们是普通人。坚持做一件事真的很难,没钱就没法坚持了。",但是话虽如此,如果没有兴趣爱好或情怀投入里面,真的能坚持吗,能做好吗?真的能挣钱吗?
不是说要去崇拜谁,迷信谁,盲从谁。做好自己就行了。
毕竟耳朵在自己身上,对了听,不对扔。仅此而已,一笑了之。
有人说我的兴趣爱好就是打牌,打游戏。但是打游戏也有以此为职业的,这告诉我们最好是能稍微的把兴趣爱好同职业结合起来,实现个人的价值和对社会的贡献。虽然每个人对人生的意义理解不尽相同,但没准这样充实和做喜欢做的事,用心做事,不管结果如何,本身就是一种意义所在。这个社会每个人都少不了柴米油盐,少有谁能像诗仙李白那样的飘柔朗逸。但是先用心把事情做好,多少投入点儿兴趣和情怀,和你追求财富并不矛盾,并且是告诉我们一个道理,切莫因急功近利而变得浮躁沉不下心来认真做事,那样只会适得其反,欲速则不达。
开源地址:
这里注意下上图的前六行,必须遵循这样的格式。分别是 文章标题,日期,简介,首部的图片展示,文章分类和作者,后面的才是文章内容。
附后台代码实现:
- 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")
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。