关于个人博客的优化

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

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

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(
                "![" + v.Name + "](" + v.Path + ")\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.关于前端的优化

统计

首先是统计:

WX20190310-082713@2x.png

看的人不多,但是接入统计是非常有用的。这里接入的是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;

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,829评论 1 331
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,603评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,846评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,600评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,780评论 3 272
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,695评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,136评论 2 293
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,862评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,453评论 0 229
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,942评论 2 233
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,347评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,790评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,293评论 3 221
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,839评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,448评论 0 181
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,564评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,623评论 2 249