关于个人博客的优化

陨石坠灭 · · 557 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

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

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

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;

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


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:陨石坠灭

查看原文:关于个人博客的优化

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

557 次点击  
加入收藏 微博
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传