refactor: eiblog

This commit is contained in:
deepzz0
2021-04-26 15:51:16 +08:00
parent bd69c62254
commit 68e01cdf1f
843 changed files with 3606 additions and 1007377 deletions

310
pkg/cache/cache.go vendored Normal file
View File

@@ -0,0 +1,310 @@
// Package cache provides ...
package cache
import (
"bytes"
"fmt"
"sort"
"strings"
"time"
"github.com/eiblog/eiblog/v2/pkg/cache/render"
"github.com/eiblog/eiblog/v2/pkg/cache/store"
"github.com/eiblog/eiblog/v2/pkg/config"
"github.com/eiblog/eiblog/v2/pkg/internal"
"github.com/eiblog/eiblog/v2/pkg/model"
"github.com/eiblog/eiblog/v2/tools"
"github.com/sirupsen/logrus"
)
var (
// Ei eiblog cache
Ei *Cache
// regenerate pages chan
pagesCh = make(chan string, 1)
pageSeries = "series-md"
pageArchive = "archive-md"
)
func init() {
store, err := store.NewStore(config.Conf.Database.Driver,
config.Conf.Database.Source)
if err != nil {
panic(err)
}
// Ei init
Ei = &Cache{
Store: store,
TagArticles: make(map[string]model.SortedArticles),
ArticlesMap: make(map[string]*model.Article),
}
err = Ei.loadAccount()
if err != nil {
panic(err)
}
// blogger
err = Ei.loadBlogger()
if err != nil {
panic(err)
}
// articles
err = Ei.loadArticles()
if err != nil {
panic(err)
}
go Ei.regeneratePages()
go Ei.timerClean()
go Ei.timerDisqus()
}
// Cache 整站缓存
type Cache struct {
store.Store
// load from db
Blogger *model.Blogger
Account *model.Account
Articles model.SortedArticles
// auto generate
PageSeries string // page
Series model.SortedSeries
PageArchives string // page
Archives model.SortedArchives
TagArticles map[string]model.SortedArticles // tagname:articles
ArticlesMap map[string]*model.Article // slug:article
}
// loadBlogger 博客信息
func (c *Cache) loadBlogger() error {
blogapp := config.Conf.BlogApp
blogger := &model.Blogger{
BlogName: blogapp.Blogger.BlogName,
SubTitle: blogapp.Blogger.SubTitle,
BeiAn: blogapp.Blogger.BeiAn,
BTitle: blogapp.Blogger.BTitle,
Copyright: blogapp.Blogger.Copyright,
}
blogger, err := c.LoadOrCreateBlogger(blogger)
if err != nil {
return err
}
c.Blogger = blogger
return nil
}
// loadAccount 账户账户信息
func (c *Cache) loadAccount() error {
blogapp := config.Conf.BlogApp
pwd := tools.EncryptPasswd(blogapp.Account.Password,
blogapp.Account.Password)
account := &model.Account{
Username: blogapp.Account.Username,
Password: pwd,
Email: blogapp.Account.Email,
PhoneN: blogapp.Account.PhoneNumber,
Address: blogapp.Account.Address,
}
account, err := c.LoadOrCreateAccount(account)
if err != nil {
return err
}
c.Account = account
return nil
}
// loadArticles 文章信息
func (c *Cache) loadArticles() error {
articles, err := c.LoadAllArticles()
if err != nil {
return err
}
sort.Sort(model.SortedArticles(articles))
for i, v := range Ei.Articles {
// 渲染页面
render.GenerateExcerptMarkdown(v)
c.ArticlesMap[v.Slug] = v
// 分析文章
if v.ID < config.Conf.BlogApp.General.StartID {
continue
}
if i > 0 {
v.Prev = Ei.Articles[i-1]
}
if Ei.Articles[i+1].ID >= config.Conf.BlogApp.General.StartID {
v.Next = Ei.Articles[i+1]
}
c.rebuildArticle(v, false)
}
// 重建专题与归档
pagesCh <- pageSeries
pagesCh <- pageArchive
return nil
}
// rebuildArticle 重建缓存tag、series、archive
func (c *Cache) rebuildArticle(article *model.Article, needSort bool) {
// tag
tags := strings.Split(article.Tags, ",")
for _, tag := range tags {
c.TagArticles[tag] = append(c.TagArticles[tag], article)
if needSort {
sort.Sort(c.TagArticles[tag])
}
}
// series
for i, series := range c.Series {
if series.ID == article.SerieID {
c.Series[i].Articles = append(c.Series[i].Articles, article)
if needSort {
sort.Sort(c.Series[i].Articles)
pagesCh <- pageSeries // 重建专题
}
}
}
// archive
y, m, _ := article.CreateTime.Date()
for i, archive := range c.Archives {
if ay, am, _ := archive.Time.Date(); y == ay && m == am {
c.Archives[i].Articles = append(c.Archives[i].Articles, article)
}
if needSort {
sort.Sort(c.Archives[i].Articles)
pagesCh <- pageArchive // 重建归档
}
return
}
// 新建归档
c.Archives = append(c.Archives, &model.Archive{
Time: article.CreateTime,
Articles: model.SortedArticles{article},
})
if needSort { // 重建归档
pagesCh <- pageArchive
}
}
// regeneratePages 重新生成series,archive页面
func (c *Cache) regeneratePages() {
for {
switch page := <-pagesCh; page {
case pageSeries:
sort.Sort(c.Series)
buf := bytes.Buffer{}
buf.WriteString(c.Blogger.SeriesSay)
buf.WriteString("\n\n")
for _, series := range c.Series {
buf.WriteString(fmt.Sprintf("### %s{#toc-%d}", series.Name, series.ID))
buf.WriteByte('\n')
buf.WriteString(series.Desc)
buf.WriteString("\n\n")
for _, article := range series.Articles {
//eg. * [标题一](/post/hello-world.html) <span class="date">(Man 02, 2006)</span>
str := fmt.Sprintf(`* [%s](/post/%s.html) <span class="date">(%s)</span>`,
article.Title, article.Slug, article.CreateTime.Format("Jan 02, 2006"))
buf.WriteString(str)
}
buf.WriteString("\n\n")
}
c.PageSeries = string(render.RenderPage(buf.Bytes()))
case pageArchive:
sort.Sort(c.Archives)
buf := bytes.Buffer{}
buf.WriteString(c.Blogger.ArchivesSay + "\n")
var (
currentYear string
gt12Month = len(Ei.Archives) > 12
)
for _, archive := range c.Archives {
if gt12Month {
year := archive.Time.Format("2006 年")
if currentYear != year {
currentYear = year
buf.WriteString(fmt.Sprintf("\n### %s\n\n", archive.Time.Format("2006 年")))
}
} else {
buf.WriteString(fmt.Sprintf("\n### %s\n\n", archive.Time.Format("2006年1月")))
}
for i, article := range archive.Articles {
if i == 0 && gt12Month {
str := fmt.Sprintf(`* *[%s](/post/%s.html) <span class="date">(%s)</span>`,
article.Title, article.Slug, article.CreateTime.Format("Jan 02, 2006"))
buf.WriteString(str)
} else {
str := fmt.Sprintf(`* [%s](/post/%s.html) <span class="date">(%s)</span>`,
article.Title, article.Slug, article.CreateTime.Format("Jan 02, 2006"))
buf.WriteString(str)
}
buf.WriteByte('\n')
}
}
c.PageArchives = string(render.RenderPage(buf.Bytes()))
}
}
}
// timerClean 定时清理文章
func (c *Cache) timerClean() {
dur := time.Duration(config.Conf.BlogApp.General.Clean)
ticker := time.NewTicker(dur * time.Hour)
for range ticker.C {
err := c.CleanArticles()
if err != nil {
logrus.Error("cache.timerClean.CleanArticles: ", err)
}
}
}
// timerDisqus disqus定时操作
func (c *Cache) timerDisqus() {
dur := time.Duration(config.Conf.BlogApp.Disqus.Interval)
ticker := time.NewTicker(dur * time.Hour)
for range ticker.C {
err := internal.PostsCount(c.ArticlesMap)
if err != nil {
logrus.Error("cache.timerDisqus.PostsCount: ", err)
}
}
}
// PageArticles 文章翻页
func (c *Cache) PageArticles(page int, pageSize int) (prev,
next int, articles []*model.Article) {
var l int
for l = len(c.Articles); l > 0; l-- {
if c.Articles[l-1].ID >= config.Conf.BlogApp.General.StartID {
break
}
}
if l == 0 {
return 0, 0, nil
}
m := l / pageSize
if d := l % pageSize; d > 0 {
m++
}
if page > m {
page = m
}
if page > 1 {
prev = page - 1
}
if page < m {
next = page + 1
}
s := (page - 1) * pageSize
e := page * pageSize
if e > l {
e = l
}
articles = c.Articles[s:e]
return
}

94
pkg/cache/render/render.go vendored Normal file
View File

@@ -0,0 +1,94 @@
// Package render provides ...
package render
import (
"regexp"
"strings"
"github.com/eiblog/eiblog/v2/pkg/config"
"github.com/eiblog/eiblog/v2/pkg/model"
"github.com/eiblog/blackfriday"
)
// blackfriday 配置
const (
commonHtmlFlags = 0 |
blackfriday.HTML_TOC |
blackfriday.HTML_USE_XHTML |
blackfriday.HTML_USE_SMARTYPANTS |
blackfriday.HTML_SMARTYPANTS_FRACTIONS |
blackfriday.HTML_SMARTYPANTS_DASHES |
blackfriday.HTML_SMARTYPANTS_LATEX_DASHES |
blackfriday.HTML_NOFOLLOW_LINKS
commonExtensions = 0 |
blackfriday.EXTENSION_NO_INTRA_EMPHASIS |
blackfriday.EXTENSION_TABLES |
blackfriday.EXTENSION_FENCED_CODE |
blackfriday.EXTENSION_AUTOLINK |
blackfriday.EXTENSION_STRIKETHROUGH |
blackfriday.EXTENSION_SPACE_HEADERS |
blackfriday.EXTENSION_HEADER_IDS |
blackfriday.EXTENSION_BACKSLASH_LINE_BREAK |
blackfriday.EXTENSION_DEFINITION_LISTS
)
var (
// 渲染markdown操作和截取摘要操作
regIdentifier = regexp.MustCompile(config.Conf.BlogApp.General.Identifier)
// header
regHeader = regexp.MustCompile("</nav></div>")
)
// IgnoreHtmlTag 去掉 html tag
func IgnoreHtmlTag(src string) string {
// 去除所有尖括号内的HTML代码
re, _ := regexp.Compile(`<[\S\s]+?>`)
src = re.ReplaceAllString(src, "")
// 去除换行符
re, _ = regexp.Compile(`\s+`)
return re.ReplaceAllString(src, "")
}
// RenderPage 渲染markdown
func RenderPage(md []byte) []byte {
renderer := blackfriday.HtmlRenderer(commonHtmlFlags, "", "")
return blackfriday.Markdown(md, renderer, commonExtensions)
}
// GenerateExcerptMarkdown 生成预览和描述
func GenerateExcerptMarkdown(article *model.Article) {
blogapp := config.Conf.BlogApp
if strings.HasPrefix(article.Content, blogapp.General.DescPrefix) {
index := strings.Index(article.Content, "\r\n")
prefix := article.Content[len(blogapp.General.DescPrefix):index]
article.Desc = IgnoreHtmlTag(prefix)
article.Content = article.Content[index:]
}
// 查找目录
content := RenderPage([]byte(article.Content))
index := regHeader.FindIndex(content)
if index != nil {
article.Header = string(content[0:index[1]])
article.Content = string(content[index[1]:])
} else {
article.Content = string(content)
}
// excerpt
index = regIdentifier.FindStringIndex(article.Content)
if index != nil {
article.Excerpt = IgnoreHtmlTag(article.Content[:index[0]])
}
uc := []rune(article.Content)
length := blogapp.General.Length
if len(uc) < length {
length = len(uc)
}
article.Excerpt = IgnoreHtmlTag(string(uc[0:length]))
}

2
pkg/cache/store/mongodb.go vendored Normal file
View File

@@ -0,0 +1,2 @@
// Package store provides ...
package store

70
pkg/cache/store/store.go vendored Normal file
View File

@@ -0,0 +1,70 @@
// Package store provides ...
package store
import (
"fmt"
"sort"
"sync"
"github.com/eiblog/eiblog/v2/pkg/model"
)
var (
storeMu sync.RWMutex
stores = make(map[string]Driver)
)
// Store 存储后端
type Store interface {
LoadOrCreateAccount(acct *model.Account) (*model.Account, error)
LoadOrCreateBlogger(blogger *model.Blogger) (*model.Blogger, error)
LoadAllArticles() ([]*model.Article, error)
UpdateAccount(name string, fields map[string]interface{}) error
UpdateBlogger(fields map[string]interface{}) error
UpdateArticle(article *model.Article) error
CleanArticles() error
}
// Driver 存储驱动
type Driver interface {
Init(source string) (Store, error)
}
// Register 注册驱动
func Register(name string, driver Driver) {
storeMu.Lock()
defer storeMu.Unlock()
if driver == nil {
panic("store: register driver is nil")
}
if _, dup := stores[name]; dup {
panic("store: register called twice for driver " + name)
}
stores[name] = driver
}
// Drivers 获取所有
func Drivers() []string {
storeMu.Lock()
defer storeMu.Unlock()
list := make([]string, 0, len(stores))
for name := range stores {
list = append(list, name)
}
sort.Strings(list)
return list
}
// NewStore 新建存储
func NewStore(name string, source string) (Store, error) {
storeMu.RLock()
driver, ok := stores[name]
storeMu.RUnlock()
if !ok {
return nil, fmt.Errorf("store: unknown driver %q (forgotten import?)", name)
}
return driver.Init(source)
}