当前位置:   article > 正文

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

golang 实现个人博客

博客演示地址: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格式的文件就是发布文章,并接收前台留言推送。练兵的意义不在于立刻投入战斗,而在于需要用到相关资源的时候能够快速战斗,而非被卡脖子。华为,就是这么有远见。这次他遇到了卡脖子,但他有技术储备,他不怕。

在扯远点儿,人,活着的意义是什么?我觉得是做你喜欢的事,做有意义的事,无悔的事。

俞敏洪说过一句话,名利和地位不是你一味的追求就能获得的,而是你坚持用心的做一件事,自然而然而来的。他就是如此,乔布斯亦是如此。之前有个访谈节目问乔布斯,为什么要创办苹果?为了钱吗?他笑了,说如果因为钱他根本没办法坚持,甚至投入精力。人,习惯于懒惰。人应该支配习惯,而决不能让习惯支配人。人的一生应该怎样的度过才有意义?《钢铁是怎样炼成的》中的保尔柯察金说:人,最宝贵的是生命,生命对于每个人只有一次。这仅有的一次生命应该怎样度过呢?每当回首往事的时候,不会因为虚度年华而悔恨,也不因碌碌无为而羞愧。

网上有人说,"俞敏洪这种公知还是算了,很多公知都有点问题,就是老想语不惊人死不休,但实际上放之四海而皆准的公理大家都懂,所以就开始搞些偷换概念的歪理,一听有点道理,一推敲就不是味儿。而且说乔布斯访谈的,估计乔布斯传都没看过,乔布斯二十出头就是亿万身家,当然不为了赚钱了,但我们是普通人。坚持做一件事真的很难,没钱就没法坚持了。",但是话虽如此,如果没有兴趣爱好或情怀投入里面,真的能坚持吗,能做好吗?真的能挣钱吗?

不是说要去崇拜谁,迷信谁,盲从谁。做好自己就行了。

毕竟耳朵在自己身上,对了听,不对扔。仅此而已,一笑了之。

有人说我的兴趣爱好就是打牌,打游戏。但是打游戏也有以此为职业的,这告诉我们最好是能稍微的把兴趣爱好同职业结合起来,实现个人的价值和对社会的贡献。虽然每个人对人生的意义理解不尽相同,但没准这样充实和做喜欢做的事,用心做事,不管结果如何,本身就是一种意义所在。这个社会每个人都少不了柴米油盐,少有谁能像诗仙李白那样的飘柔朗逸。但是先用心把事情做好,多少投入点儿兴趣和情怀,和你追求财富并不矛盾,并且是告诉我们一个道理,切莫因急功近利而变得浮躁沉不下心来认真做事,那样只会适得其反,欲速则不达。

开源地址:

tinybg: The simplest blogging systems in the world ,powered by the Golang language, with just 500 lines of background code.极小博客系统

 GitHub - yangyongzhen/tinybg: 五百行后台代码,实现一功能齐备的个人博客网站,The simplest blogging systems in the world ,powered by the Golang language, with just 500 lines of background code.极小博客系统

这里注意下上图的前六行,必须遵循这样的格式。分别是 文章标题,日期,简介,首部的图片展示,文章分类和作者,后面的才是文章内容。 

附后台代码实现:

  1. package main
  2. import (
  3. "crypto/md5"
  4. "encoding/hex"
  5. "fmt"
  6. "goblog/conf"
  7. "html/template"
  8. "io/ioutil"
  9. "net/http"
  10. "path/filepath"
  11. "sort"
  12. "strconv"
  13. "strings"
  14. "time"
  15. "github.com/gin-gonic/gin"
  16. )
  17. type Post struct {
  18. ID string //文章唯一ID 取 filename的md5
  19. Title string
  20. Date string
  21. Summary string
  22. Body string
  23. File string
  24. ImgFile string // 文章前的图片展示
  25. Item string //分类
  26. Author string //作者
  27. Cmts []conf.Comment //评论
  28. CmtCnt int //评论数量
  29. VistCnt int //浏览量
  30. }
  31. // Notice 跳转提示
  32. type Notice struct {
  33. Mess string
  34. IsSucc bool
  35. TimeOut int
  36. Href string
  37. }
  38. var articlesMap map[string]string /*创建集合 */
  39. // NewArticles 最新文章按日期排序
  40. type NewArticles []conf.Articles
  41. // HotArticles 热门文章按访问量排序
  42. type HotArticles []conf.Articles
  43. // NewArts 最新文章
  44. var NewArts NewArticles
  45. // HotArts 热门文章
  46. var HotArts HotArticles
  47. // NewPosts 总的文章,按时间排过序的
  48. var NewPosts NewArticles
  49. // NewCmts 最新评论
  50. var NewCmts []conf.Comment
  51. //文章排序的实现
  52. //文章排序
  53. //Len()
  54. func (a NewArticles) Len() int {
  55. return len(a)
  56. }
  57. //Less():将有低到高排序
  58. func (a NewArticles) Less(i, j int) bool {
  59. //fmt.Println(a[i].Date)
  60. //fmt.Println(a[j].Date)
  61. return a[i].Date > a[j].Date
  62. }
  63. //Swap()
  64. func (a NewArticles) Swap(i, j int) {
  65. a[i], a[j] = a[j], a[i]
  66. }
  67. //Len()
  68. func (a HotArticles) Len() int {
  69. return len(a)
  70. }
  71. //Less():将有低到高排序
  72. func (a HotArticles) Less(i, j int) bool {
  73. //fmt.Println(a[i].Date)
  74. //fmt.Println(a[j].Date)
  75. return a[i].VistCnt > a[j].VistCnt
  76. }
  77. //Swap()
  78. func (a HotArticles) Swap(i, j int) {
  79. a[i], a[j] = a[j], a[i]
  80. }
  81. // md5Str 计算MD5值
  82. func md5Str(str string) string {
  83. h := md5.New()
  84. h.Write([]byte(str))
  85. return hex.EncodeToString(h.Sum(nil))
  86. }
  87. // RefreshData 后台异步更新数据
  88. func RefreshData() {
  89. //全局的最新评论,只显示最新的三条
  90. curcount := len(conf.Cmt.Comts) //总的评论数量
  91. NewCmts = conf.Cmt.Comts
  92. if curcount >= 3 {
  93. NewCmts = conf.Cmt.Comts[(curcount - 3):curcount] //最新的3条评论
  94. }
  95. //post := conf.Art.ArticlesMap[conf.Item.Items[id]]
  96. NewPosts = NewArticles{}
  97. HotArts = HotArticles{}
  98. for key, value := range conf.Art.ArticlesMap {
  99. fmt.Println(key)
  100. for _, value1 := range value {
  101. NewPosts = append(NewPosts, value1)
  102. HotArts = append(HotArts, value1)
  103. }
  104. }
  105. sort.Sort(NewPosts) //日期排序
  106. sort.Sort(HotArts) //访问量排序
  107. //fmt.Println("IS Sorted?", sort.IsSorted(post))
  108. NewArts = NewPosts
  109. num := len(NewPosts)
  110. if num > 9 {
  111. NewArts = NewPosts[0:9]
  112. HotArts = HotArts[0:9]
  113. }
  114. conf.Art.Save()
  115. conf.Stat.Save()
  116. }
  117. func handleIndex(c *gin.Context) {
  118. fmt.Printf("%#v\n", NewPosts)
  119. spage := c.DefaultQuery("page", "1")
  120. fmt.Printf("cur page:%s\n", spage)
  121. page, _ := strconv.Atoi(spage)
  122. nums := len(NewPosts)
  123. allpage := nums / 5
  124. if nums%5 != 0 {
  125. allpage = nums/5 + 1
  126. }
  127. fmt.Printf("all page num:%d\n", allpage)
  128. posts := NewPosts
  129. if (page * 5) < nums {
  130. posts = NewPosts[(page-1)*5 : page*5]
  131. } else {
  132. posts = NewPosts[(page-1)*5 : nums]
  133. }
  134. tabs := make([]int, allpage+2) //分页表
  135. if (page - 1) == 0 {
  136. tabs[0] = 1
  137. } else {
  138. tabs[0] = page - 1
  139. }
  140. for i := 1; i <= allpage; i++ {
  141. tabs[i] = i
  142. }
  143. if page+1 <= allpage {
  144. tabs[allpage+1] = page + 1
  145. } else {
  146. tabs[allpage+1] = 1
  147. }
  148. fmt.Printf("tabs:%#v\n", tabs)
  149. conf.Stat.ToCnt++
  150. ip := c.ClientIP()
  151. fmt.Printf("client ip:%s\n", ip)
  152. conf.Stat.IPs = append(conf.Stat.IPs, ip)
  153. 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})
  154. }
  155. // strTrip 去除字符串的空格和换行
  156. func strTrip(src string) string {
  157. str := strings.Replace(src, " ", "", -1)
  158. str = strings.Replace(str, "\r", "", -1)
  159. str = strings.Replace(str, "\n", "", -1)
  160. return str
  161. }
  162. func getPosts() []Post {
  163. a := []Post{}
  164. files, _ := filepath.Glob("posts/*")
  165. for i, f := range files {
  166. fmt.Println(i)
  167. fmt.Println(f)
  168. file := strings.Replace(f, "posts\\", "", -1)
  169. fmt.Println(file)
  170. file = strings.Replace(file, ".md", "", -1)
  171. fileread, _ := ioutil.ReadFile(f)
  172. lines := strings.Split(string(fileread), "\n")
  173. title := string(lines[0])
  174. date := strTrip(string(lines[1]))
  175. summary := string(lines[2])
  176. imgfile := string(lines[3])
  177. item := strTrip(string(lines[4]))
  178. author := string(lines[5])
  179. imgfile = strTrip(imgfile)
  180. body := ""
  181. //body := strings.Join(lines[4:len(lines)], "\n")
  182. fmt.Println(imgfile)
  183. id := md5Str(file)
  184. articlesMap[id] = f
  185. itemcount := len(conf.Item.Items)
  186. //查找分类看是否已存在
  187. if itemcount == 0 {
  188. conf.Item.Items = append(conf.Item.Items, item)
  189. } else {
  190. k := 0
  191. for k = 0; k < itemcount; k++ {
  192. if conf.Item.Items[k] == item {
  193. break
  194. }
  195. }
  196. if k >= itemcount {
  197. //分类之前未存在,添加分类
  198. conf.Item.Items = append(conf.Item.Items, item)
  199. itemcount = len(conf.Item.Items)
  200. }
  201. }
  202. for j := 0; j < itemcount; j++ {
  203. if conf.Item.Items[j] == item {
  204. art := conf.Articles{id, item, title, date, summary, file, imgfile, author, 0, 0}
  205. if conf.Art.ArticlesMap[item] == nil {
  206. conf.Art.ArticlesMap[item] = make(map[string]conf.Articles)
  207. }
  208. _, exists := conf.Art.ArticlesMap[item][id]
  209. if !exists {
  210. conf.Art.ArticlesMap[item][id] = art
  211. }
  212. break
  213. }
  214. }
  215. 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})
  216. }
  217. conf.Art.Save()
  218. conf.Item.Save()
  219. fmt.Printf("%#v\n", conf.Art.ArticlesMap)
  220. fmt.Printf("%#v\n", articlesMap)
  221. return a
  222. }
  223. func handleArticles(c *gin.Context) {
  224. id := c.Param("id")
  225. file := articlesMap[id]
  226. fmt.Println(id)
  227. fmt.Println(file)
  228. fileread, _ := ioutil.ReadFile(file)
  229. lines := strings.Split(string(fileread), "\n")
  230. title := string(lines[0])
  231. date := string(lines[1])
  232. summary := string(lines[2])
  233. imgfile := string(lines[3])
  234. item := strTrip(string(lines[4]))
  235. author := string(lines[5])
  236. imgfile = strTrip(imgfile)
  237. body := strings.Join(lines[5:len(lines)], "\n")
  238. //fmt.Println(body)
  239. //fmt.Printf("%#v\n", conf.Cmt.Comts)
  240. cmts := []conf.Comment{}
  241. count := 0 //评论数量
  242. for i, v := range conf.Cmt.Comts {
  243. //fmt.Printf("%#v\n", c)
  244. if v.ID == id {
  245. cmts = append(cmts, conf.Cmt.Comts[i])
  246. count++
  247. }
  248. }
  249. //文章浏览量++
  250. art := conf.Art.ArticlesMap[item][id]
  251. art.VistCnt++ //浏览数量加一
  252. conf.Art.ArticlesMap[item][id] = art
  253. go RefreshData()
  254. p := Post{id, title, date, summary, body, file, imgfile, item, author, cmts, conf.Art.ArticlesMap[item][id].CmtCnt, conf.Art.ArticlesMap[item][id].VistCnt}
  255. 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})
  256. }
  257. func handleItems(c *gin.Context) {
  258. sid := c.Param("id")
  259. id, _ := strconv.Atoi(sid)
  260. all := conf.Art.ArticlesMap[conf.Item.Items[id]]
  261. fmt.Printf("%#v\n", all)
  262. posts := []conf.Articles{}
  263. // 遍历 hash
  264. for _, value := range all {
  265. posts = append(posts, value)
  266. }
  267. //支持分页
  268. spage := c.DefaultQuery("page", "1")
  269. fmt.Printf("cur page:%s\n", spage)
  270. page, _ := strconv.Atoi(spage)
  271. nums := len(posts)
  272. allpage := nums / 5
  273. if nums%5 != 0 {
  274. allpage = nums/5 + 1
  275. }
  276. fmt.Printf("all page num:%d\n", allpage)
  277. curposts := posts
  278. if (page * 5) < nums {
  279. curposts = posts[(page-1)*5 : page*5]
  280. } else {
  281. curposts = posts[(page-1)*5 : nums]
  282. }
  283. tabs := make([]int, allpage+2) //分页表
  284. if (page - 1) == 0 {
  285. tabs[0] = 1
  286. } else {
  287. tabs[0] = page - 1
  288. }
  289. for i := 1; i <= allpage; i++ {
  290. tabs[i] = i
  291. }
  292. if page+1 <= allpage {
  293. tabs[allpage+1] = page + 1
  294. } else {
  295. tabs[allpage+1] = 1
  296. }
  297. fmt.Printf("tabs:%#v\n", tabs)
  298. 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})
  299. }
  300. func handlePostComment(c *gin.Context) {
  301. sid := c.PostForm("id") //文章ID
  302. item := c.PostForm("item") //文章分类
  303. item = strTrip(item)
  304. title := c.PostForm("title") //文章标题
  305. text := c.PostForm("text") //评论内容
  306. author := c.PostForm("author") //评论者
  307. email := c.PostForm("email") //邮箱
  308. url := c.PostForm("url") //评论者网址
  309. time := time.Now().Format("2006-01-02 15:04:05")
  310. fmt.Println(sid, item, text, author, email, url, time)
  311. href := "/article/" + sid
  312. notice := Notice{"提交成功", true, 3, href}
  313. art := conf.Art.ArticlesMap[item][sid]
  314. art.CmtCnt++ //评论数量加一
  315. conf.Art.ArticlesMap[item][sid] = art
  316. //fmt.Printf("%#v\n", conf.Art.ArticlesMap[item][sid])
  317. fmt.Println(conf.Art.ArticlesMap[item][sid].CmtCnt)
  318. //fmt.Printf("%#v\n", conf.Art.ArticlesMap[item])
  319. cmt := conf.Comment{sid, item, title, author, email, text, time, url}
  320. fmt.Printf("%#v\n", cmt)
  321. conf.Cmt.Comts = append(conf.Cmt.Comts, cmt)
  322. conf.Cmt.Save()
  323. //conf.Art.Save()
  324. go RefreshData()
  325. c.HTML(http.StatusOK, "success.html", gin.H{"notice": notice})
  326. }
  327. // Add ...模板里使用加
  328. func Add(a, b int) int {
  329. return a + b
  330. }
  331. // Dec ...模板里使用减
  332. func Dec(a, b int) int {
  333. return a - b
  334. }
  335. func main() {
  336. router := gin.Default()
  337. //增加几个模板自定义函数Add Dec
  338. router.SetFuncMap(template.FuncMap{
  339. "add": Add,
  340. "dec": Dec,
  341. })
  342. //关于
  343. conf.Abt.Name = "一米阳光"
  344. conf.Abt.Jobs = "嵌入式 linux | Android | go web"
  345. conf.Abt.WX = "yongzhen1111"
  346. conf.Abt.QQ = "534117529"
  347. conf.Abt.Email = "534117529@qq.com"
  348. conf.Abt.Save()
  349. //分类
  350. // conf.Item.Items = append(conf.Item.Items, "随笔")
  351. // fmt.Printf("%d\n", len(conf.Item.Items))
  352. //加载分类
  353. conf.Item.Load()
  354. //加载评论
  355. conf.Cmt.Load()
  356. //加载文章
  357. conf.Art.Load()
  358. //加载统计信息
  359. conf.Stat.Load()
  360. articlesMap = make(map[string]string)
  361. articleIndex := getPosts()
  362. fmt.Printf("%#v\n", articleIndex)
  363. //更新文章排序和浏览量
  364. RefreshData()
  365. //静态文件
  366. router.Static("/assets", "./static")
  367. //渲染html页面
  368. router.LoadHTMLGlob("views/*")
  369. router.GET("/", handleIndex)
  370. router.GET("/article/:id", handleArticles)
  371. router.GET("/items/:id", handleItems)
  372. router.POST("/comment", handlePostComment)
  373. //运行的端口
  374. router.Run(":8000")
  375. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/457388
推荐阅读
相关标签
  

闽ICP备14008679号