?? 個人博客學(xué)術(shù)化及接口權(quán)限改造

博客傳送門

說實(shí)話又一年沒寫博客了,不過終于畢業(yè)了,也許以后可以有更多的時間寫寫博客(tao。

趁著還沒入職的間隙,做了一些博客改造的開發(fā)工作,其中運(yùn)維:JavaScript:Python:Java 工作量占比大概 5:3:2:1,總共間斷的開發(fā)了兩周多。

?? Motivations

想必各位 NLPer 看蘇神博客的時候一定被其豐富的知識量和閱讀體驗(yàn)極佳的公式震撼過。

出于這點(diǎn)考慮,本期的學(xué)術(shù)化改造的一個目的就是提供良好的公式編譯能力,增強(qiáng)對 \LaTeX 的支持。

其二,作為學(xué)術(shù)類博客,需要提升評論及引用能力,提供相應(yīng)的 Bib\TeX

其三,更清晰的 Tag 維護(hù)策略,Tag 之間存在層級關(guān)系。

除此之外,放開靜態(tài)文件爬取限制,收緊 API 接口權(quán)限,實(shí)現(xiàn)動態(tài)管理,(其實(shí)這個一直想做的,只是沒想到做起來那么花時間。

?? Developing

前三條,都是 JavaScript 的工作。
由于歷史原因,本博客用的還是 Vuepress@0.16,看了一下 v1 版本 lib 代碼改動不大,等 v2 發(fā) beta 版了有空再改吧(能用就將就用著。

MathJax

有別于蘇神使用 php 開發(fā)的博客,Vuepress 是一個基于 markdown-it 的框架。
而 Markdown 天生就和$\LaTeX$不搭,比如說_在 Markdown 中表示 $x_i + y_i$ 會被編譯為x<em>i + y</em>i。

而我們的目標(biāo)是支持 MathJax 而不是 katex 這種閹割版本。
參考 Yihui Xie 在The Best Way to Support LaTeX Math in Markdown with MathJax提供的思路,
利用<code>標(biāo)簽提供一個不會被 markdown-it 侵入的環(huán)境,再對 markdown-it 編譯好的 body 做 code 標(biāo)簽解除,以便 MathJax 的 JavaScript 代碼能夠渲染相應(yīng)公式區(qū)域。

replaceLatexCode(){
    var i, text, code, codes = document.getElementsByTagName('code');
    for (i = 0; i < codes.length;) {
        code = codes[i];
        if (code.parentNode.tagName !== 'PRE' && code.childElementCount === 0) {
        text = code.textContent;
        if (/^\$[^$]/.test(text) && /[^$]\$$/.test(text)) {
            text = text.replace(/^\$/, '\\(').replace(/\$$/, '\\)');
            code.textContent = text;
        }
        if (/^\\\((.|\s)+\\\)$/.test(text) || /^\\\[(.|\s)+\\\]$/.test(text) ||
            /^\$(.|\s)+\$$/.test(text) ||
            /^\\begin\{([^}]+)\}(.|\s)+\\end\{[^}]+\}$/.test(text)) {
            code.outerHTML = code.innerHTML;  // remove <code></code>
            continue;
        }
        }
        i++;
    }
},

不過需要注意的是 Vuepress 中聯(lián)系訪問頁面是 SPA,進(jìn)入下一頁之后需要重新做上述操作。

 getMathJax() {
        const script1 = document.createElement('script');
        script1.src = 'https://xxxx/MathJax-2.7.4/AMS-setcounter.js';
        script1.type = 'text/javascript';
        script1.id = "ams-counter";
        setTimeout(() => document.body.appendChild(script1), 500);
        const script2 = document.createElement('script');
        script2.type = 'text/javascript';
        script2.src = 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.4/MathJax.js?config=TeX-AMS-MML_HTMLorMML';
        script2.id = "tex-ams";
        setTimeout(() => document.body.appendChild(script2), 700);
        setTimeout(() => document.getElementById("ams-counter").remove(), 2000);
        setTimeout(() => document.getElementById("tex-ams").remove(), 2000);
      },

(其中AMS-setcounter.js是借鑒蘇神的配置。
然后公式的顏色感覺還是藍(lán)色最合適,其他看的都容易困(主要是太菜了

使用的時候一定要帶上在反引號中帶上 dollar 符或者\begin{}\end{} (單行公式在博客里閱讀體驗(yàn)++

$bib\TeX$ 和 Utterence

(可能是畢業(yè)論文寫傻了,自然而然的想到應(yīng)該給博客加一個bib\TeX的功能。

這點(diǎn)技術(shù)難度幾乎沒有,只不過為了更好的支持中文人名(Last Name, First Name)的形式,將原先 config.js 中的 author 拓展成 authorLastName 和 authorFirstName。

需要注意的是同樣由于 SPA 也需要對反復(fù)渲染,(我這邊實(shí)現(xiàn)的比較丑陋,沒有用成 vue 中的 watch。

此外對于評論模塊做了遷移,之前一直使用的是 Gitalk,也在利用 Gitalk 給 Vuepress 搭建的 blog 增加評論功能介紹了 Vuepress 中的實(shí)現(xiàn)。

它確實(shí)樣式美觀,GitHub Issue 的方式既能控制評論權(quán)限也能方便數(shù)據(jù)遷移。
但是它的缺點(diǎn)十分明顯:

  1. 暴露 Github Token;
  2. 必須每篇 blog 都建立一個 Issue 即使是沒有人評論的;

第二點(diǎn)是我不太能忍的,主要是有一直看 Issue 的習(xí)慣。大家也知道,我以前一直是用一個 Issue 管理所有博客的評論,這對之前評論的人也是一種打擾。
我設(shè)想中應(yīng)該是主動 Push 型的,有人評論再去建 Issue,本來已經(jīng)想自己實(shí)現(xiàn)了,結(jié)果發(fā)現(xiàn)有一個叫 Utterence 的插件完美的實(shí)現(xiàn)了我的需求。

借助 Bot 的形式也能規(guī)避 GitHub Token 的暴露。

不過它使用 iframe 的形式,讓自定義 style 變成了不可能,所幸 PC 端和移動端的樣式也還能接受。

同樣因?yàn)?SPA 需要反復(fù)渲染。

Multi-level Tags

考慮到 Tags 這個東西實(shí)際上是類似于 Key Word 的東西,像 ACM 系列的會議/期刊都會按多級的方式顯示以便不同興趣的學(xué)者能快速定位到相應(yīng)的文章。

之前實(shí)現(xiàn)的是單一級的 Tags,導(dǎo)致 Tags 并不能展示文章的分類信息。

于是實(shí)現(xiàn)了一個多級(考慮到實(shí)際使用需求最大級數(shù)定為 3 級),并支持葉子層多 Tags 的方式,例如NLP/LM/(KnowledgeInject#KnowledgeBase)就表示了NLP/LM/KnowledgeInjectNLP/LM/KnowledgeBase兩條三級路徑。

實(shí)際上是一個多叉樹結(jié)構(gòu),需要對樹做建樹和深度遍歷(Tags 頁線性展示),大概是一個 LeetCode Easy 的題。

export function dfsTagGraph(root, tagG, done) {
  const queue = [];
  const res = [];
  queue.push([root, 1]);
  while (queue.length) {
    var top = queue.pop();
    if (done.has(top[0])) {
      continue;
    }
    done.add(top[0]);
    res.push(top);
    const children = tagG[top[0]] || [];
    children.forEach(c => {
      if (!done.has(c)) {
        queue.push([c, top[1] + 1]);
      }
    })
  }
  return res;
}

同樣需要注意的是需要反復(fù)渲染。

權(quán)限管理

(事先說明,這個策略還是很簡陋的,請各位安全大佬手下留情 ??

下面來到最重頭的權(quán)限管理,這其實(shí)是我一直想做的,之前從Nginx 配置Nginx 日志分析兩方面做了限制。

但是這個限制是天粒度的,定時跑腳本也行,但是對于時間邊界整體是不敏感的。

究其原因,是因?yàn)?Log 是以文件形式存放,失去了流屬性和時間屬性。

ELK

調(diào)研之后,使用 ElasticSearch + Kibana + Filebeat 方案。
Filebeat 監(jiān)聽 file 當(dāng) file 開始更新的時候會分配一個 registrar 去執(zhí)行相應(yīng) pipeline。

Filebeat 是極其輕量的,可以定義一些 js 腳本處理格式等問題,自身也提供 Module 以供進(jìn)行解析常見軟件 log。
推薦這種方式,以 Nginx Access Log 為例,還會對 Ip 進(jìn)行 geo 分析,對 User-Agent 也會進(jìn)行解析,這節(jié)省了我們大量分析工作。
(當(dāng)然 Nginx 自身也提供 Geo 不過需要你安裝特定的插件。

由于 Filebeat 是輕量的,這些 machine_learning Jobs 都是推到 ElasticSearch 上由 Java 來執(zhí)行的。
所以如果你想利用 Module 由 Filebeat 傳到 Redis 的話,那得自己做 geo 和 UA 解析。

整體架構(gòu)如下圖所示,

在這里插入圖片描述

大部分時間花在搭建和配置上了,即使是用 docker。貼一下搭建過程中踩的坑

  1. ES 和 Kibana 默認(rèn)只開放本地操作權(quán)限,如要 remote 操作,需要設(shè)定 host:"0.0.0.0";
  2. 我是用一臺機(jī)子放 ES + Kibana,然后兩臺機(jī)子 Filebeat 到 ES 機(jī)子上。通過 Nginx 轉(zhuǎn)發(fā) ES 和 Kibana 請求,大部分均正常,只有在 Kibana 下少部分 POST 會返回 413,這就導(dǎo)致 FileBeat 連接帶不同主機(jī)(不同內(nèi)網(wǎng)域)的 Kibana 時候必須開個端口用域名連接。(此處控制變量判斷問題源)
  3. Kibana 和 ES,F(xiàn)ilebeat 的密碼相關(guān)設(shè)定不要明文寫在文件中,但是不同的 keystore 接口不同,Kibana 的 KEY 直接是 config 的 key(也只能是)

ELK 之后還可以做很多應(yīng)用,比如說數(shù)據(jù)上報(bào)實(shí)時展示,通過 Kibana 的 timelion 進(jìn)行繪圖,(這又是另外一個坑了

在這里插入圖片描述

除了 ES 提供的數(shù)據(jù),還需要進(jìn)一步擴(kuò)充數(shù)據(jù)源。

  1. 明確 IP 對應(yīng)服務(wù)器用途,利用 Ip2Proxy 查詢 geo 解析得到的 asn 所對應(yīng)的臨近 IP 端用途,如果 IP 端較近則認(rèn)為用途一致;
  2. 收集所有 Header 信息,這個需要配置 Nginx 啟動 Lua,參考StackOverFlow.
  3. 相對應(yīng)的 Filebeat Inject 也需要做對應(yīng)的修改

nginx.conf 配置如下

log_format  myformat escape=none '$remote_addr $host $hostname $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for" "$http_cookie" @$request_headers@';
access_log  logs/access.log  myformat;

set_by_lua_block $request_headers{
    local h = ngx.req.get_headers()
    local request_headers_all = ""
    for k, v in pairs(h) do
        local rowtext = ""
        rowtext = string.format("[%s:%s] ", k, v)
        request_headers_all = request_headers_all .. rowtext
    end
    return request_headers_all
}

相對應(yīng)的 Filebeat 的 Inject Pipeline 配置

(%{NGINX_HOST} )?\"?(?:%{NGINX_ADDRESS_LIST:nginx.access.remote_ip_list}|%{NOTSPACE:source.address}) (-|%{DATA:url.host}) (-|%{DATA:user.name})  \\[%{HTTPDATE:nginx.access.time}\\] \"%{DATA:nginx.access.info}\" %{NUMBER:http.response.status_code:long} %{NUMBER:http.response.body.bytes:long} \"(-|%{DATA:http.request.referrer})\" \"(-|%{DATA:user_agent.original})\" \"(-|%{DATA:header.x_forward})\" \"(-|%{DATA:header.cookie})\" @(-|%{DATA:header.original})@

當(dāng)獲得數(shù)據(jù)之后,定時獲取區(qū)間數(shù)據(jù),進(jìn)行策略分析(需要控制度)和 Ipset 封禁。

此外還在服務(wù)端增設(shè) Cookies 用以輔助權(quán)限管理。

總結(jié)

本文針對本博客從 MathJax、Tags、評論、bib\TeX四方面做學(xué)術(shù)化改造,并利用 ElasticSearch + Kibana + Filebeat 提出一種權(quán)限管理策略。
水平有限,歡迎討論。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容