简单的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下载地址

相关文章
相关标签/搜索