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")
}
相关文章
|
20小时前
|
数据采集 人工智能 搜索推荐
快速入门:利用Go语言下载Amazon商品信息的步骤详解
本文探讨了使用Go语言和代理IP技术构建高效Amazon商品信息爬虫的方法。Go语言因其简洁语法、快速编译、并发支持和丰富标准库成为理想的爬虫开发语言。文章介绍了电商网站的发展趋势,如个性化推荐、移动端优化和跨境电商。步骤包括设置代理IP、编写爬虫代码和实现多线程采集。提供的Go代码示例展示了如何配置代理、发送请求及使用goroutine进行多线程采集。注意需根据实际情况调整代理服务和商品URL。
快速入门:利用Go语言下载Amazon商品信息的步骤详解
|
2天前
|
存储 编译器 Go
Go语言学习12-数据的使用
【5月更文挑战第5天】本篇 Huazie 向大家介绍 Go 语言数据的使用,包含赋值语句、常量与变量、可比性与有序性
38 6
Go语言学习12-数据的使用
|
3天前
|
Java Go
一文带你速通go语言指针
Go语言指针入门指南:简述指针用于提升效率,通过地址操作变量。文章作者sharkChili是Java/CSDN专家,维护Java Guide项目。文中介绍指针声明、取值,展示如何通过指针修改变量值及在函数中的应用。通过实例解析如何使用指针优化函数,以实现对原变量的直接修改。作者还邀请读者加入交流群深入探讨,并鼓励关注其公众号“写代码的SharkChili”。
9 0
|
3天前
|
存储 缓存 Java
来聊聊go语言的hashMap
本文介绍了Go语言中的`map`与Java的不同设计思想。作者`sharkChili`是一名Java和Go开发者,同时也是CSDN博客专家及JavaGuide项目的维护者。文章探讨了Go语言`map`的数据结构,包括`count`、`buckets指针`和`bmap`,解释了键值对的存储方式,如何利用内存对齐优化空间使用,并展示了`map`的初始化、插入键值对以及查找数据的源码过程。此外,作者还分享了如何通过汇编查看`map`操作,并鼓励读者深入研究Go的哈希冲突解决和源码。最后,作者提供了一个交流群,供读者讨论相关话题。
14 0
|
4天前
|
Java Go
Go语言学习11-数据初始化
【5月更文挑战第3天】本篇带大家通过内建函数 new 和 make 了解Go语言的数据初始化过程
17 1
Go语言学习11-数据初始化
|
4天前
|
自然语言处理 安全 Java
速通Go语言编译过程
Go语言编译过程详解:从词法分析(生成token)到句法分析(构建语法树),再到语义分析(类型检查、推断、匹配及函数内联)、生成中间码(SSA)和汇编码。最后,通过链接生成可执行文件。作者sharkchili,CSDN Java博客专家,分享技术细节,邀请读者加入交流群。
22 2
|
5天前
|
Java Linux Go
一文带你速通Go语言基础语法
本文是关于Go语言的入门介绍,作者因其简洁高效的特性对Go语言情有独钟。文章首先概述了Go语言的优势,包括快速上手、并发编程简单、设计简洁且功能强大,以及丰富的标准库。接着,文章通过示例展示了如何编写和运行Go代码,包括声明包、导入包和输出语句。此外,还介绍了Go的语法基础,如变量类型(数字、字符串、布尔和复数)、变量赋值、类型转换和默认值。文章还涉及条件分支(if和switch)和循环结构(for)。最后,简要提到了Go函数的定义和多返回值特性,以及一些常见的Go命令。作者计划在后续文章中进一步探讨Go语言的其他方面。
10 0
|
6天前
|
JavaScript 前端开发 Go
Go语言的入门学习
【4月更文挑战第7天】Go语言,通常称为Golang,是由Google设计并开发的一种编程语言,它于2009年公开发布。Go的设计团队主要包括Robert Griesemer、Rob Pike和Ken Thompson,这三位都是计算机科学和软件工程领域的杰出人物。
14 1
|
6天前
|
Go
|
7天前
|
分布式计算 Java Go
Golang深入浅出之-Go语言中的分布式计算框架Apache Beam
【5月更文挑战第6天】Apache Beam是一个统一的编程模型,适用于批处理和流处理,主要支持Java和Python,但也提供实验性的Go SDK。Go SDK的基本概念包括`PTransform`、`PCollection`和`Pipeline`。在使用中,需注意类型转换、窗口和触发器配置、资源管理和错误处理。尽管Go SDK文档有限,生态系统尚不成熟,且性能可能不高,但它仍为分布式计算提供了可移植的解决方案。通过理解和掌握Beam模型,开发者能编写高效的数据处理程序。
135 1