简单的markdown在线解析服务-pro

前言

前一个简单版本简单的markdown在线解析服务

说明

这个版本之所以加一个pro的后缀,是因为增加了一下功能。

  • 增加了/update接口锁,保证同一时间只会有一个groutine在更新文件列表

  • 增加了markdown解析后数据的缓存,避免重复解析

  • 增加了单个markdown文件的读取锁,避免同一个文件正在解析,重复提交解析任务

文件结构

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

dependences

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

实现

manager.go

markdows路径在New()时传入,一般情况是在main运行的根目录建立一个专门用来存放markdown文件的文件夹。

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在更新列表,那自己就不必更新了;

  • markdownFile的读取锁比上面稍微复杂了一点点。因为在尝试失败后不能直接跳出(还是需要返回数据的),所以就在尝试获取所失败了一次后,通过Lock方法来等待实际读取文件的groutine退出;

  • 做缓存时,文件列表中就不能直接保存markdownFile的实例了。如果这样的话,从列表中获取的就是实力的copy,自然缓存也无从谈起,所以只能存引用。

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

ps: 这个版本比上一个高级了一些,其实对于这个项目的实际运用场景,并没有创造额外价值,毕竟实际访问量就那么几个。

ennn

以下是代码地址,增加了一些易用性相关的东西,可执行文件可以直接运行,template都打包到文件里了release下载地址

转载于:https://my.oschina.net/u/3703365/blog/1802398

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值