Init v3.2
This commit is contained in:
238
models/articles.go
Normal file
238
models/articles.go
Normal file
@@ -0,0 +1,238 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"blog/config"
|
||||
"blog/utils"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Time time.Time
|
||||
|
||||
type Article struct {
|
||||
Title string `json:"title"`
|
||||
Date Time `json:"date"`
|
||||
Description string `json:"description"`
|
||||
Tags []string `json:"tags"`
|
||||
Author string `json:"author"`
|
||||
MusicId string `json:"musicId"`
|
||||
Path string
|
||||
ShortUrl string
|
||||
Category string
|
||||
}
|
||||
|
||||
type Articles []Article
|
||||
|
||||
type ArticleDetail struct {
|
||||
Article
|
||||
Body string
|
||||
}
|
||||
|
||||
func initArticlesAndImages(dir string) (Articles, map[string]string, error) {
|
||||
var articles Articles
|
||||
shortUrlMap := make(map[string]string)
|
||||
|
||||
articles, err := RecursiveReadArticles(dir)
|
||||
if err != nil {
|
||||
return articles, shortUrlMap, err
|
||||
}
|
||||
sort.Sort(articles)
|
||||
for i := len(articles) - 1; i >= 0; i-- {
|
||||
//这里必须使用倒序的方式生成 shortUrl,因为如果有相同的文章标题,
|
||||
// 倒序会将最老的文章优先生成shortUrl,保证和之前的 shortUrl一样
|
||||
article := articles[i]
|
||||
keyword := utils.GenerateShortUrl(article.Title, func(url, keyword string) bool {
|
||||
//保证 keyword 唯一
|
||||
_, ok := shortUrlMap[keyword]
|
||||
return !ok
|
||||
})
|
||||
articles[i].ShortUrl = keyword
|
||||
shortUrlMap[keyword] = article.Path
|
||||
}
|
||||
return articles, shortUrlMap, nil
|
||||
}
|
||||
|
||||
func ArticleSearch(articles *Articles, search string, category string, tag string) Articles {
|
||||
|
||||
var articleList Articles
|
||||
for _, article := range *articles {
|
||||
|
||||
pass := true
|
||||
|
||||
if search != "" && strings.Index(article.Title, search) == -1 {
|
||||
pass = false
|
||||
}
|
||||
if category != "" && strings.Index(article.Category, category) == -1 {
|
||||
pass = false
|
||||
}
|
||||
if tag != "" {
|
||||
pass = false
|
||||
for _, tagx := range article.Tags {
|
||||
if strings.Index(tagx, tag) != -1 {
|
||||
pass = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if pass {
|
||||
articleList = append(articleList, article)
|
||||
}
|
||||
|
||||
}
|
||||
return articleList
|
||||
}
|
||||
|
||||
func RecursiveReadArticles(dir string) (Articles, error) {
|
||||
|
||||
var articles Articles
|
||||
|
||||
dirInfo, err := os.Stat(dir)
|
||||
|
||||
if err != nil {
|
||||
return articles, err
|
||||
}
|
||||
if !dirInfo.IsDir() {
|
||||
return articles, errors.New("目标不是一个目录")
|
||||
}
|
||||
|
||||
fileOrDir, err := ioutil.ReadDir(dir)
|
||||
if err != nil {
|
||||
return articles, err
|
||||
}
|
||||
|
||||
for _, fileInfo := range fileOrDir {
|
||||
name := fileInfo.Name()
|
||||
path := dir + "/" + name
|
||||
upperName := strings.ToUpper(name)
|
||||
if fileInfo.IsDir() {
|
||||
subArticles, err := RecursiveReadArticles(path)
|
||||
if err != nil {
|
||||
return articles, err
|
||||
}
|
||||
articles = append(articles, subArticles...)
|
||||
} else if strings.HasSuffix(upperName, ".MD") {
|
||||
article, err := ReadArticle(path)
|
||||
if err != nil {
|
||||
return articles, err
|
||||
}
|
||||
articles = append(articles, article)
|
||||
} else if strings.HasSuffix(upperName, ".PNG") ||
|
||||
strings.HasSuffix(upperName, ".GIF") ||
|
||||
strings.HasSuffix(upperName, ".JPG") {
|
||||
|
||||
dst := config.Cfg.CurrentDir + "/images/" + name
|
||||
fmt.Println(utils.IsFile(dst))
|
||||
if !utils.IsFile(dst) {
|
||||
_, _ = utils.CopyFile(path, dst)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return articles, nil
|
||||
}
|
||||
|
||||
func ReadArticle(path string) (Article, error) {
|
||||
article, _, err := readMarkdown(path)
|
||||
if err != nil {
|
||||
return article, err
|
||||
}
|
||||
return article, nil
|
||||
}
|
||||
|
||||
func ReadArticleDetail(path string) (ArticleDetail, error) {
|
||||
_, articleDetail, err := readMarkdown(path)
|
||||
if err != nil {
|
||||
return articleDetail, err
|
||||
}
|
||||
return articleDetail, nil
|
||||
}
|
||||
|
||||
func readMarkdown(path string) (Article, ArticleDetail, error) {
|
||||
var article Article
|
||||
var articleDetail ArticleDetail
|
||||
mdFile, err := os.Stat(path)
|
||||
|
||||
if err != nil {
|
||||
return article, articleDetail, err
|
||||
}
|
||||
if mdFile.IsDir() {
|
||||
return article, articleDetail, errors.New("this path is Dir")
|
||||
}
|
||||
markdown, err := ioutil.ReadFile(path)
|
||||
|
||||
if err != nil {
|
||||
return article, articleDetail, err
|
||||
}
|
||||
markdown = bytes.TrimSpace(markdown)
|
||||
|
||||
article.Path = path
|
||||
article.Category = GetCategoryName(path)
|
||||
article.Title = strings.TrimSuffix(strings.ToUpper(mdFile.Name()), ".MD")
|
||||
article.Date = Time(mdFile.ModTime())
|
||||
|
||||
if !bytes.HasPrefix(markdown, []byte("```json")) {
|
||||
article.Description = cropDesc(markdown)
|
||||
articleDetail.Article = article
|
||||
articleDetail.Body = string(markdown)
|
||||
return article, articleDetail, nil
|
||||
}
|
||||
|
||||
markdown = bytes.Replace(markdown, []byte("```json"), []byte(""), 1)
|
||||
markdownArrInfo := bytes.SplitN(markdown, []byte("```"), 2)
|
||||
|
||||
article.Description = cropDesc(markdownArrInfo[1])
|
||||
|
||||
if err := json.Unmarshal(bytes.TrimSpace(markdownArrInfo[0]), &article); err != nil {
|
||||
article.Title = "文章[" + article.Title + "]解析 JSON 出错,请检查。"
|
||||
article.Description = err.Error()
|
||||
return article, articleDetail, nil
|
||||
}
|
||||
article.Path = path
|
||||
article.Title = strings.ToUpper(article.Title)
|
||||
|
||||
articleDetail.Article = article
|
||||
articleDetail.Body = string(markdownArrInfo[1])
|
||||
return article, articleDetail, nil
|
||||
}
|
||||
|
||||
func cropDesc(c []byte) string {
|
||||
content := []rune(string(c))
|
||||
contentLen := len(content)
|
||||
|
||||
if contentLen <= config.Cfg.DescriptionLen {
|
||||
return string(content[0:contentLen])
|
||||
}
|
||||
|
||||
return string(content[0:config.Cfg.DescriptionLen])
|
||||
}
|
||||
|
||||
func (t *Time) UnmarshalJSON(b []byte) error {
|
||||
date, err := time.ParseInLocation(`"`+config.Cfg.TimeLayout+`"`, string(b), time.Local)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
*t = Time(date)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t Time) MarshalJSON() ([]byte, error) {
|
||||
|
||||
return []byte(t.Format(`"` + config.Cfg.TimeLayout + `"`)), nil
|
||||
}
|
||||
|
||||
func (t Time) Format(layout string) string {
|
||||
return time.Time(t).Format(layout)
|
||||
}
|
||||
|
||||
func (a Articles) Len() int { return len(a) }
|
||||
|
||||
func (a Articles) Less(i, j int) bool { return time.Time(a[i].Date).After(time.Time(a[j].Date)) }
|
||||
|
||||
func (a Articles) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
69
models/category.go
Normal file
69
models/category.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"blog/config"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Category struct {
|
||||
Name string
|
||||
Quantity int
|
||||
Articles Articles
|
||||
}
|
||||
type Categories []Category
|
||||
|
||||
func GetCategoryName(path string) string {
|
||||
var categoryName string
|
||||
newPath := strings.Replace(path, config.Cfg.DocumentContentDir+"/", "", 1)
|
||||
|
||||
if strings.Index(newPath, "/") == -1 { //文件在根目录下(content/)没有分类名称
|
||||
categoryName = "未分类"
|
||||
} else {
|
||||
categoryName = strings.Split(newPath, "/")[0]
|
||||
}
|
||||
return categoryName
|
||||
}
|
||||
|
||||
func GroupByCategory(articles *Articles, articleQuantity int) Categories {
|
||||
|
||||
var categories Categories
|
||||
categoryMap := make(map[string]Articles)
|
||||
|
||||
for _, article := range *articles {
|
||||
|
||||
_, existedCategory := categoryMap[article.Category]
|
||||
if existedCategory {
|
||||
categoryMap[article.Category] = append(categoryMap[article.Category], article)
|
||||
} else {
|
||||
categoryMap[article.Category] = Articles{article}
|
||||
}
|
||||
}
|
||||
for categoryName, articles := range categoryMap {
|
||||
articleLen := len(articles)
|
||||
|
||||
var articleList Articles
|
||||
if articleQuantity <= 0 {
|
||||
articleList = articles
|
||||
} else {
|
||||
if articleQuantity > articleLen {
|
||||
articleList = articles[0:articleLen]
|
||||
} else {
|
||||
articleList = articles[0:articleQuantity]
|
||||
}
|
||||
}
|
||||
categories = append(categories, Category{
|
||||
Name: categoryName,
|
||||
Quantity: articleLen,
|
||||
Articles: articleList,
|
||||
})
|
||||
}
|
||||
sort.Sort(categories)
|
||||
return categories
|
||||
}
|
||||
|
||||
func (c Categories) Len() int { return len(c) }
|
||||
|
||||
func (c Categories) Less(i, j int) bool { return c[i].Quantity > c[j].Quantity }
|
||||
|
||||
func (c Categories) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
|
50
models/common.go
Normal file
50
models/common.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"blog/config"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var Navigation Navs
|
||||
var ArticleList Articles
|
||||
var ArticleShortUrlMap map[string]string //用来保证文章 shortUrl 唯一和快速定位文章
|
||||
var Template HtmlTemplate
|
||||
|
||||
func CompiledContent() {
|
||||
config.Initial() //克隆或者更新文档库
|
||||
//下面是对内容的生成
|
||||
wg := sync.WaitGroup{}
|
||||
var err error
|
||||
//导航
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
Navigation, err = initExtraNav(config.Cfg.DocumentExtraNavDir)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
//加载html模板
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
Template, err = initHtmlTemplate(config.Cfg.ThemesDir)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
//文章
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
ArticleList, ArticleShortUrlMap, err = initArticlesAndImages(config.Cfg.DocumentContentDir)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
wg.Wait()
|
||||
//启用并发比之前节约4倍左右的时间
|
||||
return
|
||||
}
|
31
models/extra_nav.go
Normal file
31
models/extra_nav.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Nav struct {
|
||||
Title string
|
||||
Path string
|
||||
}
|
||||
type Navs []Nav
|
||||
|
||||
func initExtraNav(dir string) (Navs, error) {
|
||||
|
||||
var navigation Navs
|
||||
var extraNav Articles
|
||||
|
||||
extraNav, err := RecursiveReadArticles(dir)
|
||||
if err != nil {
|
||||
return navigation, err
|
||||
}
|
||||
sort.Sort(extraNav)
|
||||
|
||||
for _, article := range extraNav {
|
||||
title := strings.Title(strings.ToLower(article.Title))
|
||||
navigation = append(navigation, Nav{Title: title, Path: article.Path})
|
||||
}
|
||||
|
||||
return navigation, nil
|
||||
}
|
91
models/html_template.go
Normal file
91
models/html_template.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"blog/config"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
)
|
||||
|
||||
type TemplatePointer struct {
|
||||
*template.Template
|
||||
}
|
||||
|
||||
type HtmlTemplate struct {
|
||||
Article TemplatePointer
|
||||
Categories TemplatePointer
|
||||
Dashboard TemplatePointer
|
||||
ExtraNav TemplatePointer
|
||||
Index TemplatePointer
|
||||
}
|
||||
|
||||
func (t TemplatePointer) WriteData(w io.Writer, data interface{}) {
|
||||
|
||||
err := t.Execute(w, data)
|
||||
if err != nil {
|
||||
if _, e := w.Write([]byte(err.Error())); e != nil {
|
||||
fmt.Println(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t TemplatePointer) WriteError(w io.Writer, err error) {
|
||||
if _, e := w.Write([]byte(err.Error())); e != nil {
|
||||
fmt.Println(e)
|
||||
}
|
||||
}
|
||||
|
||||
func BuildViewData(title string, data interface{}) map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"Title": title,
|
||||
"Data": data,
|
||||
"Config": config.Cfg,
|
||||
"Navs": Navigation,
|
||||
}
|
||||
}
|
||||
|
||||
func initHtmlTemplate(viewDir string) (HtmlTemplate, error) {
|
||||
var htmlTemplate HtmlTemplate
|
||||
|
||||
tp, err := readHtmlTemplate(
|
||||
[]string{"index", "extraNav", "dashboard", "categories", "article"},
|
||||
viewDir)
|
||||
if err != nil {
|
||||
return htmlTemplate, err
|
||||
}
|
||||
|
||||
htmlTemplate.Index = tp[0]
|
||||
htmlTemplate.ExtraNav = tp[1]
|
||||
htmlTemplate.Dashboard = tp[2]
|
||||
htmlTemplate.Categories = tp[3]
|
||||
htmlTemplate.Article = tp[4]
|
||||
|
||||
return htmlTemplate, nil
|
||||
}
|
||||
|
||||
func SpreadDigit(n int) []int {
|
||||
var r []int
|
||||
for i := 1; i <= n; i++ {
|
||||
r = append(r, i)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func readHtmlTemplate(htmlFileName []string, viewDir string) ([]TemplatePointer, error) {
|
||||
var htmlTemplate []TemplatePointer
|
||||
|
||||
head := viewDir + "/layouts/head.html"
|
||||
footer := viewDir + "/layouts/footer.html"
|
||||
|
||||
for _, name := range htmlFileName {
|
||||
|
||||
tp, err := template.New(name+".html").
|
||||
Funcs(template.FuncMap{"SpreadDigit": SpreadDigit}).
|
||||
ParseFiles(viewDir+"/"+name+".html", head, footer)
|
||||
if err != nil {
|
||||
return htmlTemplate, err
|
||||
}
|
||||
htmlTemplate = append(htmlTemplate, TemplatePointer{tp})
|
||||
}
|
||||
return htmlTemplate, nil
|
||||
}
|
48
models/pagination.go
Normal file
48
models/pagination.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"math"
|
||||
)
|
||||
|
||||
type PageResult struct {
|
||||
List Articles `json:"list"`
|
||||
Total int `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"pageSize"`
|
||||
TotalPage int
|
||||
}
|
||||
|
||||
func Pagination(articles *Articles, page int, pageSize int) PageResult {
|
||||
|
||||
articleLen := len(*articles)
|
||||
totalPage := int(math.Floor(float64(articleLen / pageSize)))
|
||||
|
||||
if (articleLen % pageSize) != 0 {
|
||||
totalPage++
|
||||
}
|
||||
result := PageResult{
|
||||
Total: articleLen,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
TotalPage: totalPage,
|
||||
}
|
||||
if page < 1 {
|
||||
result.Page = 1
|
||||
}
|
||||
if page > result.TotalPage {
|
||||
result.Page = result.TotalPage
|
||||
}
|
||||
|
||||
if articleLen <= result.PageSize {
|
||||
result.List = (*articles)[0:articleLen]
|
||||
} else {
|
||||
startNum := (result.Page - 1) * result.PageSize
|
||||
endNum := startNum + result.PageSize
|
||||
if endNum > articleLen {
|
||||
endNum = articleLen
|
||||
}
|
||||
result.List = (*articles)[startNum:endNum]
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
Reference in New Issue
Block a user