簡單的markdown在線解析服務-pro

前言

前一個簡單版本簡單的markdown在線解析服務html

說明

這個版本之因此加一個pro的後綴,是由於增長了一下功能。前端

  • 增長了/update接口鎖,保證同一時間只會有一個groutine在更新文件列表git

  • 增長了markdown解析後數據的緩存,避免重複解析github

  • 增長了單個markdown文件的讀取鎖,避免同一個文件正在解析,重複提交解析任務緩存

文件結構

--manager
  manager.go
--markdowns
--templates
  index.html
main.gomarkdown

dependences

"github.com/LK4D4/trylock"
	"github.com/microcosm-cc/bluemonday"
	"gopkg.in/russross/blackfriday.v2"

實現

manager.go

markdows路徑在New()時傳入,通常狀況是在main運行的根目錄創建一個專門用來存放markdown文件的文件夾。app

package manager

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"sync"

	"github.com/LK4D4/trylock"
	"github.com/microcosm-cc/bluemonday"
	blackfriday "gopkg.in/russross/blackfriday.v2"
)

// MarkdownFile :markdown file and html byte code
type MarkdownFile struct {
	path       string
	data       []byte
	updateLock *trylock.Mutex
}

// MarkdownsManeger to update and get markdown file
type MarkdownsManeger struct {
	rwLock       *sync.RWMutex
	updateLock   *trylock.Mutex
	data         map[string]*MarkdownFile
	markdownPath string
}

func (m *MarkdownFile) isExist() bool {
	return m.data != nil
}

func (m *MarkdownFile) readMarkdown() ([]byte, error) {
	if m.updateLock.TryLock() {
		defer m.updateLock.Unlock()
		if fileread, err := ioutil.ReadFile(m.path); err == nil {
			unsafe := blackfriday.Run(fileread)
			html := bluemonday.UGCPolicy().SanitizeBytes(unsafe)
			m.data = html
			return html, nil
		} else {
			return nil, fmt.Errorf("file(%s)ReadFail", m.path)
		}
	} else {
		m.updateLock.Lock()
		m.updateLock.Unlock()
		if m.isExist() {
			return m.data, nil
		} else {
			return nil, fmt.Errorf("file(%s)ReadFail", m.path)
		}
	}

}

func (s *MarkdownsManeger) Reflesh() bool {
	if s.updateLock.TryLock() {
		s.rwLock.Lock()
		defer s.updateLock.Unlock()
		defer s.rwLock.Unlock()
		s.data = make(map[string]*MarkdownFile)
		files, _ := filepath.Glob(fmt.Sprintf("./%s/*", s.markdownPath))
		for _, f := range files {
			fileName := f[strings.LastIndex(f, string(os.PathSeparator))+1 : len(f)-3]
			s.data[fileName] = &MarkdownFile{f, nil, new(trylock.Mutex)}
		}
		return true

	}
	return false
}

func (s *MarkdownsManeger) GetFileList() []string {
	keys := make([]string, 0, len(s.data))
	for k := range s.data {
		keys = append(keys, k)
	}
	return keys
}

func (s *MarkdownsManeger) GetFile(fileName string) ([]byte, error) {
	s.rwLock.RLock()
	defer s.rwLock.RUnlock()
	markdownFile, ok := s.data[fileName]
	if !ok {
		return nil, fmt.Errorf("file(%s)NotExist", fileName)
	}
	if markdownFile.isExist() {
		return markdownFile.data, nil
	}
	return markdownFile.readMarkdown()
}

//New MarkdownsManeger
func New(markdownPath string) MarkdownsManeger {
	return MarkdownsManeger{
		new(sync.RWMutex),
		new(trylock.Mutex),
		map[string]*MarkdownFile{},
		markdownPath,
	}
}

main.go

package main

import (
	"fmt"
	"html/template"
	"net/http"
	"os"

	"./manager"
)

type MarkdownsHandler struct {
	markdownsManeger *manager.MarkdownsManeger
	templatesPath    string
}

func (h *MarkdownsHandler) Index(w http.ResponseWriter, req *http.Request) {
	t := template.New("index.html")
	t, _ = t.ParseFiles(fmt.Sprintf("%s%sindex.html", h.templatesPath, string(os.PathSeparator)))
	t.Execute(w, h.markdownsManeger.GetFileList())
}

func (h *MarkdownsHandler) ReaderHander(w http.ResponseWriter, r *http.Request) {
	fileName := r.URL.Query().Get("file")
	body, err := h.markdownsManeger.GetFile(fileName)
	if err != nil {
		w.WriteHeader(http.StatusNotFound)
	} else {
		w.Write(body)
	}
}
func (h *MarkdownsHandler) UpdateHandler(w http.ResponseWriter, req *http.Request) {
	if h.markdownsManeger.Reflesh() {
		w.Write([]byte("success"))
	} else {
		w.Write([]byte("under refleshing"))
	}

}

var (
	hander *MarkdownsHandler
)

func init() {
	m := manager.New("markdowns")
	hander = &MarkdownsHandler{
		&m,
		"templates",
	}
	hander.markdownsManeger.Reflesh()
}

func main() {
	http.HandleFunc("/", hander.Index)
	http.HandleFunc("/read", hander.ReaderHander)
	http.HandleFunc("/update", hander.UpdateHandler)

	http.ListenAndServe(":8000", nil)
}

index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <h1>markdownlist</h1>
  {{range $key := .}}
  <a href="/read?file={{$key}}">{{$key}}</a><br/>
  {{end}}
</body>
</html>

小結

  • 此次項目主要用到的就是tryLock(天然是直接用現成的第三方包),更新文件列表時回去嘗試得到鎖,失敗時說明已經有groutine在更新列表,那本身就沒必要更新了;ui

  • markdownFile的讀取鎖比上面稍微複雜了一點點。由於在嘗試失敗後不能直接跳出(仍是須要返回數據的),因此就在嘗試獲取所失敗了一次後,經過Lock方法來等待實際讀取文件的groutine退出;.net

  • 作緩存時,文件列表中就不能直接保存markdownFile的實例了。若是這樣的話,從列表中獲取的就是實力的copy,天然緩存也無從談起,因此只能存引用。ssr

ps: 前端樣式美化我就不搞了

ps: 這個版本比上一個高級了一些,其實對於這個項目的實際運用場景,並無創造額外價值,畢竟實際訪問量就那麼幾個。

ennn

如下是代碼地址,增長了一些易用性相關的東西,可執行文件能夠直接運行,template都打包到文件裏了release下載地址

相關文章
相關標籤/搜索