Blog/models/articles.go

248 lines
5.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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, key string) (ArticleDetail, error) {
_, articleDetail, err := readMarkdown(path, key)
if err != nil {
return articleDetail, err
}
return articleDetail, nil
}
// readMarkdown 读取 Markdown 文件内容
func readMarkdown(path, key 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 文件内容
markdown, err := ioutil.ReadFile(path)
if err != nil {
return article, articleDetail, err
}
markdown = bytes.TrimSpace(markdown)
// 设置文章属性
if key != "" {
article.ShortUrl = key
}
article.Path = path
article.Category = GetCategoryName(path)
article.Title = strings.TrimSuffix(strings.ToUpper(mdFile.Name()), ".MD")
article.Date = Time(mdFile.ModTime())
// 处理 开头JSON 格式的文章信息
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])
// 解析 JSON 内容
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] }