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] }