前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >关于个人博客的优化

关于个人博客的优化

作者头像
陨石坠灭
发布2020-01-21 16:01:32
2.3K0
发布2020-01-21 16:01:32
举报
文章被收录于专栏:全栈之路全栈之路全栈之路

作为一名懂前端的程序员,天天在嘴上谈样式,可是自己的个人博客网站却没有时间打理。就好像农民伯伯把最好的菜卖给别人,让自己的傻儿子却吃“长势不太好”的蔬菜,可农民伯伯其实是非常心疼自己的孩子的。

好了,废话不多说,先来看看成果吧。

pc端效果
pc端效果

样式借鉴了tower —— 一款团队任务管理的产品的样式,非常的简洁干净。然后,同时对移动端进行了适配:

移动端效果
移动端效果

简介

该博客是根据开源项目deepzz0/goblog修改而来。服务器端采用go语言,使用beego作为服务器端框架,前端采用bootstrap,采用golang模板技术,同时原项目使用了docker,但docker部分被我弃用了。

github地址:https://github.com/deepzz0/goblog

首先,让我介绍一下该项目的一些优势吧。

优势
  1. 功能齐全,基本可以满足个人博客的所有需求
  2. 运行在docker上,可以不关心操作系统的一些差异
  3. 数据库采用mongodb,更改数据库和表结构非常容易,而且向前兼容比较实现。
  4. 前端采用bootstrap,兼容移动端
  5. 采用beego和golang模板技术,而且开发时修改网页代码,刷新后立即见效,大大提高了开发效率。
  6. 配置文件齐全,可以高度定制自己的专属博客
  7. 后台管理功能齐全,同时有统计功能
  8. 博客采用markdown编辑

那么,有啥缺点呢?

缺点
  1. 界面有些丑陋
  2. 采用docker,没有安装docker,所以带来了一系列问题(主要还是环境变量已经文件路径的问题)
  3. markdown编辑不支持文件上传以及全屏编辑,且编辑器所依赖的库太久,有些markdown语法不支持

总之,该项目非常值得借鉴,接下来就讲一下遇到的问题,以及解决的方案。

遇到的问题及解决方案

1. 环境变量

os.Setenv("MGO", "127.0.0.1")

由于之前采用docker:

ENV MGO 192.168.0.1

现在改如何转变呢?

首先是开发中,由于采用VSCode编辑器,自然支持运行时支持环境变量的设置,launch.json

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Launch project",
      "type": "go",
      "request": "launch",
      "mode": "auto",
      "remotePath": "",
      "port": 2345,
      "host": "127.0.0.1",
      "program": "${workspaceRoot}/src",
      "env": {
        "MGO": "127.0.0.1",
        "CON_PATH": "${workspaceRoot}"
      },
      "args": [],
      "showLog": true
    }
  ]
}

其次,是在项目部署时,需要提前设置环境变量:

export MGO="127.0.0.1"
export CON_PATH="$HOME/git/goblog"

2. 让markdown支持图片插入

首先要支持,图片的显示,这里就直接略过。

其次,需要能让编辑器插入图片文本:

$("#editor-area").insertAtCaret(
                "![博客头像](https://avatars2.githubusercontent.com/u/20502528?s=460&v=4)\n"
);
$("#editor-area").change();

这里#editor-areaarea文本编辑框,后面调用change事件,是为了该控件能够触发onChange事件。

然后就是文件上传了,这里讲一下服务器是如何接上图片的:

type Response struct {
	Status int
	Data   interface{}
	Err    Error
}
type Error struct {
	Level string
	Msg   string
}
...

func NewResponse() *Response {
	return &Response{Status: RS.RS_success}
}

func (m *MaterialController) Post() {
	resp := NewResponse()
	defer resp.WriteJson(m.Ctx.ResponseWriter)
	flag := m.GetString("flag")
        var allfiles = m.Ctx.Request.MultipartForm.File
	var keys []string
	var files []*multipart.FileHeader
	for k, vals := range allfiles {
		keys = append(keys, k)
		files = append(files, vals...)
	}

	if !dir.IsExist(models.ResTmpPath) {
		err := os.MkdirAll(models.ResTmpPath, 777)
		if err != nil {
			resp.Status = RS.RS_failed
			resp.Err = helper.Error{Level: helper.WARNING, Msg: "临时目录创建失败。"}
			return
		}
	}

	// var retArray []interface{}
	for i, h := range files {
		f, err := h.Open()
		defer f.Close()
		if err != nil {
			resp.Status = RS.RS_failed
			resp.Err = helper.Error{Level: helper.WARNING, Msg: "文件上传失败。"}
			return
		}
		path := models.ResTmpPath + "/" + h.Filename

		dst, err := os.Create(path)
		defer dst.Close()

		if err != nil {
			resp.Status = RS.RS_failed
			resp.Err = helper.Error{Level: helper.WARNING, Msg: "文件上传失败。"}
			return
		}

		io.Copy(dst, f)
		logd.Infof("文件上传:%d,%s", i, path)
	}

}

3. 关于文章摘要提取以及图片的提取

采用golang的正则表达式来提取,正则表达式的妙用就不多说了,直接上代码。

import (
	"fmt"
	"regexp"
	"strings"
	"gopkg.in/russross/blackfriday.v2"
)
...
// 解析成html
	p := bluemonday.UGCPolicy()
	p.AllowAttrs("class").Matching(regexp.MustCompile("^language-[a-zA-Z0-9]+$")).OnElements("code")

	html := string(p.SanitizeBytes(blackfriday.Run([]byte(markDownText))))

这是将markdown文本转化为HTML代码。

// 提取摘要
	reg, _ := regexp.Compile(`<[^>]+>`)
	pre := reg.ReplaceAllString(html, "")

	rs := []rune(pre)
	min := func(a int, b int) int {
		if a > b {
			return b
		}
		return a
	}
	l := 120
	preview = string(rs[:min(len(rs), l)]) //+ "..."
	if len(rs) > l {
		preview = preview + "..."
	}

这就是提取摘要的方法,其实就是去掉<HTML标签>,然后加上省略号。

// 提取图片路径
	var getURL = func(html string, num int) []string {
		regURL := regexp.MustCompile(`<img [^>]*src="([^>"]+)"[^>]*>`)
		var arr = regURL.FindAllSubmatch([]byte(html), -1)
		var URLs = make([]string, 0)

		for i, v := range arr {
			if len(v) > 1 && i < num {
				URLs = append(URLs, string(v[1]))
			}
		}
		return URLs
	}

imageURLs = getURL(html, 3)

这是提取HTML<img>中的(最多3个)链接,不过这个是有问题的,HTML代码的一些符号被转义了,如:< : &lt;,因此这里需要采用原生的markdown文本来提取链接:[图片上传失败...(image-6e30dd-1552179914203)]

// 提取图片路径
	var getURL = func(html string, num int) []string {
		regURL := regexp.MustCompile(`![[][^]]*[]][(]([^()]*)[)]`)
		var arr = regURL.FindAllSubmatch([]byte(html), -1)
		var URLs = make([]string, 0)

		for i, v := range arr {
			if len(v) > 1 && i < num {
				URLs = append(URLs, string(v[1]))
			}
		}
		return URLs
	}

imageURLs = getURL(markDownText, 3)

是不是[]()有点傻傻弄不清呢,其实呢,这个只要多试几次,总能够找到提取的方法的,这个正则表达式的提取部分为:([^()]*),即小括号中的内容,只不过为了区分链接与图片链接,所以才这么多波折。哈哈,终于写成别人也看不懂的正则表达式了,好开森\^0\^。

4.关于markdown的“编译”

这里更新到了markdown的最新的库,但是呢,功能还是有些偏弱。最典型的就是对表格的支持和对列的支持都偏弱。对于表格的支持:--不能支持,只能写成---;对于列的支持,必须换行,也就是上一行不能有内容。

所以,在js层提交markdown文本提交的时候做了一下处理,处理如下:

/**
 * 修正md5部分代码无法解析的问题

 */
function correctionTopicMd5(e) {
  var content = $(e).val();
  if (!content) return;
  // 修复表格无法解析的问题 以及列表需要换行的问题
  content = content
    .replace(/\n--\|/g, "\n---|")
    .replace(/\|--\n/g, "|---\n")
    .replace(/\|--\|/g, "|---|")

    .replace(/\n(.+)\n([\-\*] )/g, "\n$1\n\n$2")
    .replace(/([\-\*] .+)\n(.+)\n/g, "$1\n\n$2\n");
  $(e).val(content);
}

correctionTopicMd5("#editor-area");

这里采用的是js的正则表达式,有没有感觉正则表达式的妙用无穷呢?

嗯,为了加深正则表达式的印象,这里举几个栗子,关于正则表达式在VSCode中重构代码时的使用吧。

5. 拓展:正则表达式的替换

换行缩进

查找:\n+
替换:\n

这个命令可以执行多次,最终的效果就是将多行空行转化为一行空行。

数组分段

将字母A,B,C,D,...,Z按每行4列展开

解决方案:

查找:(([^,]+[,]){4})
替换:$1\n

Key-Value位置替换

{
    int[] age,
    long time,
    string name
}

替换为

{
    age: int[],
    time: long,
    name: string
}

解决方案:

查找:([\w\[\]]+) ([\w]+)
替换:$2: $1

常量替换

const RED:string = "red";
const YELLOW:string = "yellow";
const BLUE:string = "blue";
const BLACK:string = "black";
const WHITE:string = "white";
...

替换一系列常量:

原本:var color = RED;
目标:var color = tran(RED);

解决方案:

如果有前缀,会比较好处理,可是没有前缀怎么办呢?

查找:= (RED|YELLOW|BLUE|BLACK|WHITE)
替换:= tran($1)

去掉所有小数后面多余的0

0.0000100000
0.000 aaa
0.12300
0.bbb
0.00233
123000bb
1.000100vvv

替换为:

0.00001
0 aaa
0.123
0bbb
0.00233
123000bb
1.0001vvv

解决方案:

查找:(\.|(\..+?))[0]*([^0-9]*)$
替换:$2$3

只要找到待替换文本得异同,然后用正则表达式匹配出来,轻易就能够完成替换。值得注意的是:不要把非目标替换文本匹配进去。

6.一键切换网页模板

重构代码最最重要的原则就是随时可以终止。所以,一般我们在重构代码的时候,会设置一个开关,以便切换为原来的版本。

由于博客采用了新的样式,所以之前的页面不能用了,这时候就需要想办法,但是这样才能做到这么多网页一个个的修改呢。答案很简单,采用配置文件就行了。

这里展示一下我新增的网页配置文件吧,tmpcontroller.yaml:

mode: new
old:
  page404: views/404.html
  home: homelayout.html
  homePage: homeTemplate.html
  about: aboutTemplate.html
  group: groupTemplate.html
  login: login.html
  message: messageTemplate.html
  useragent: plugin/useragent.html
  topic: topicTemplate.html
new:
  page404: views/404.html
  home: sp/homelayout.html
  homePage: sp/homeTemplate.html
  about: sp/aboutTemplate.html
  group: sp/groupTemplate.html
  login: login.html
  message: sp/messageTemplate.html
  useragent: plugin/useragent.html
  topic: sp/topicTemplate.html

这样的话,只要改变mode的值就可以切换页面的指向了,其实这也算是给博客定义多个主题了。至于怎样加载yaml配置文件这里就不多讲了,毕竟想法更重要。

7.关于前端的优化

统计

首先是统计:

看的人不多,但是接入统计是非常有用的。这里接入的是google分析

至于如何接入呢,其实很简单,不过最终是否成功,还在于你是否能够翻越那一道qiang。

首先就是去注册,网址:https://analytics.google.com/analytics/web/#

然后就是将代码嵌入到你的网页中:

<script>
    (function (i, s, o, g, r, a, m) {
      i['GoogleAnalyticsObject'] = r; i[r] = i[r] || function () {
        (i[r].q = i[r].q || []).push(arguments)
      }, i[r].l = 1 * new Date(); a = s.createElement(o),

        m = s.getElementsByTagName(o)[0]; a.async = 1; a.src = g; m.parentNode.insertBefore(a, m)
    })(window, document, 'script', '/static/js/analytics.js', 'ga');

    ga('create', '<你的ID>', 'auto');
    ga('send', 'pageview');
  </script>
  <script type="text/javascript">
    $('#btn-search').on('click', function () {
      var content = $('#search-content').val();
      if (content == "") {
        pushMessage('info', "sorry|请输入你搜索的标题。")
        return;
      }
      location.href = "/search?title=" + content;
    });
  </script>
  <!-- Global site tag (gtag.js) - Google Analytics -->

  <script async src="https://www.googletagmanager.com/gtag/js?id=<你的ID>"></script>
  <script>
    window.dataLayer = window.dataLayer || [];
    function gtag() { dataLayer.push(arguments); }
    gtag('js', new Date());

    gtag('config', '<你的ID>');
  </script>

这里的ID可以查询到,如果想进一步拓展,可以查看Google分析的文档。

分词与不分词

这个是什么意思呢?其实就是换行时,是否需要保持词语的完整性。比如,标签,那就不能让标签的字在换行时被拆开,这时候,应该采用如下样式:

.tag{
  word-break: keep-all;
}

这样之所以采用keep-all,主要是因为中文分词无效,就是单独的字。

然后,就是代码部分:<pre><code>...</code></pre>,代码样式可以采用单词分词:

pre{   
    word-break: keep-all;
    word-wrap: break-word; // 只对英文起作用,以单词作为换行依据。
    white-space: pre-wrap; //只对中文起作用,强制换行。
}

当然如果不采用换行也是可以的,这样就需要支持横向滚动:

pre{
  pverflow-x:auto;
}

关于文章图片的嵌入

查看博客图片样例,可以看到,图片其实是嵌入到文章的,那这是怎样做到的呢。

首先,是HTML代码,记得图片一定要在文本内容前面哦。

<div class="topic">
       <p>
           <a class="img" href="{{.URL}}">
            {{ range .ImageURLs }}
                  <img src="{{ . }}" />
            {{end}}
            {{.Preview}}
           </a>
       </p>
</div>

这是golang的模板语法,.代码当前元素。可以看到,图片是在文本内容{{.Preview}}前面的。

那么接下来就是样式了。

.topic {
    display: inline-block;
    width: 100%;
}
.topic p {
    margin: 6px 0px;
    font-size: 13px;
    line-height: 24px;
    color: #999;
}

.topic a.img {
    width: 100%;
    text-decoration: none;
    color: #666;
    word-break: break-all;
}

.topic a.img img {
    width: auto;
    height: auto;
    max-width: 25%;
    max-height: 100px;
    float: right;
    overflow: hidden;
    text-align: center;
    background-color: #f0f0f0;
    border-radius: 4px;
    border: 1px solid #f0f0f0;
}

可以看到,图片采用了右浮动,另外宽高都是auto,只限定了最大宽度和最大高度,这样的好处是,图片是等比例缩放的。

关于返回到顶部按钮

$(window).scroll(function () {
        if($(window).scrollTop()>=100 && !$(".go-top").is(':visible')) {
            $(".go-top").fadeIn().css("display","inline-block");;
        }else if($(window).scrollTop()<100 && $(".go-top").is(':visible')){
            $(".go-top").fadeOut();
        }
    });
    $(".go-top").click(function(event){ 
        $('html,body').animate({scrollTop:0}, 100);
        return false;
    });

这是返回顶部按钮的代码,但是呢,博客在移动端显示时,却出现按钮无法显示的问题,只要原因是移动端滚动层不再是全局。所以为了兼容移动端,添加了对移动端返回到顶部的支持:

function bindScroll(e) {
  $(e).scroll(function() {
    if ($(e).scrollTop() >= 100 && !$(".go-top").is(":visible")) {
      $(".go-top")
        .fadeIn()
        .css("display", "inline-block");
    } else if ($(e).scrollTop() < 100 && $(".go-top").is(":visible")) {
      $(".go-top").fadeOut();
    }
  });
}
bindScroll(window);
bindScroll("#scroll-dev");

$(".go-top").click(function(event) {
  $("html,body").animate({ scrollTop: 0 }, 100);
  $("#scroll-dev").animate({ scrollTop: 0 }, 100);
  return false;
});

好了,讲了这么多,接下来就再讲一点点吧。

那就是例图中的搜索,可以看见,没有搜索按钮,那怎么提交呢?其实很简单,只需要按回车就行了。

<form action="javascript:void(0)" id="search-content" method="GET">
       <input type="text" placeholder="搜索文章" />
</form>

js代码:

var content = $("#search-content input")
    .eq(0)
    .val();
  if (content == "") {
    alert("info", "sorry|请输入你搜索的标题。");
    return;
  }
  location.href = "/search?title=" + content;

哈哈,如果你看这里,那么恭喜你,我已经没什么要讲的了。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019/03/10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 简介
    • 优势
      • 缺点
      • 遇到的问题及解决方案
      相关产品与服务
      容器镜像服务
      容器镜像服务(Tencent Container Registry,TCR)为您提供安全独享、高性能的容器镜像托管分发服务。您可同时在全球多个地域创建独享实例,以实现容器镜像的就近拉取,降低拉取时间,节约带宽成本。TCR 提供细颗粒度的权限管理及访问控制,保障您的数据安全。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档