前一個簡單版本簡單的markdown在線解析服務html
這個版本之因此加一個pro的後綴,是由於增長了一下功能。前端
增長了/update接口鎖,保證同一時間只會有一個groutine在更新文件列表git
增長了markdown解析後數據的緩存,避免重複解析github
增長了單個markdown文件的讀取鎖,避免同一個文件正在解析,重複提交解析任務緩存
--manager
manager.go
--markdowns
--templates
index.html
main.gomarkdown
"github.com/LK4D4/trylock" "github.com/microcosm-cc/bluemonday" "gopkg.in/russross/blackfriday.v2"
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, } }
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) }
<!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: 這個版本比上一個高級了一些,其實對於這個項目的實際運用場景,並無創造額外價值,畢竟實際訪問量就那麼幾個。
如下是代碼地址,增長了一些易用性相關的東西,可執行文件能夠直接運行,template都打包到文件裏了release下載地址