248 lines
5.8 KiB
Go
248 lines
5.8 KiB
Go
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] }
|