前一个简单版本简单的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下载地址