mirror of
https://github.com/eiblog/eiblog.git
synced 2026-02-04 13:52:26 +08:00
592 lines
14 KiB
Go
592 lines
14 KiB
Go
// Package main provides ...
|
|
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"regexp"
|
|
"sort"
|
|
// "strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/EiBlog/blackfriday"
|
|
"github.com/EiBlog/eiblog/helper"
|
|
"github.com/EiBlog/eiblog/setting"
|
|
"github.com/EiBlog/utils/logd"
|
|
db "github.com/EiBlog/utils/mgo"
|
|
"gopkg.in/mgo.v2"
|
|
"gopkg.in/mgo.v2/bson"
|
|
)
|
|
|
|
const (
|
|
DB = "eiblog"
|
|
COLLECTION_ACCOUNT = "account"
|
|
COLLECTION_ARTICLE = "article"
|
|
COUNTER_TAG = "tag"
|
|
COUNTER_SERIE = "serie"
|
|
COUNTER_ARTICLE = "article"
|
|
SERIES_MD = "series_md"
|
|
ARCHIVE_MD = "archive_md"
|
|
ADD = "add"
|
|
DELETE = "delete"
|
|
)
|
|
|
|
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
|
|
)
|
|
|
|
// Global Account
|
|
var (
|
|
Ei *Account
|
|
lock sync.Mutex
|
|
)
|
|
|
|
func init() {
|
|
// 数据库加索引
|
|
ms, c := db.Connect(DB, COLLECTION_ACCOUNT)
|
|
index := mgo.Index{
|
|
Key: []string{"username"},
|
|
Unique: true,
|
|
DropDups: true,
|
|
Background: true,
|
|
Sparse: true,
|
|
}
|
|
if err := c.EnsureIndex(index); err != nil {
|
|
logd.Fatal(err)
|
|
}
|
|
ms.Close()
|
|
ms, c = db.Connect(DB, COLLECTION_ARTICLE)
|
|
index = mgo.Index{
|
|
Key: []string{"id", "slug"},
|
|
Unique: true,
|
|
DropDups: true,
|
|
Background: true,
|
|
Sparse: true,
|
|
}
|
|
if err := c.EnsureIndex(index); err != nil {
|
|
logd.Fatal(err)
|
|
}
|
|
ms.Close()
|
|
// 读取帐号信息
|
|
Ei = loadAccount()
|
|
// 获取文章
|
|
Ei.Articles = loadArticles()
|
|
// 生成markdown文档
|
|
go generateMarkdown()
|
|
// 启动定时器
|
|
go timer()
|
|
// 获取评论数量
|
|
// go CommentsCount()
|
|
}
|
|
|
|
// 读取或初始化帐号信息
|
|
func loadAccount() (a *Account) {
|
|
a = &Account{}
|
|
err := db.FindOne(DB, COLLECTION_ACCOUNT, bson.M{"username": setting.Conf.Account.Username}, a)
|
|
// 初始化用户数据
|
|
if err == mgo.ErrNotFound {
|
|
a = &Account{
|
|
Username: setting.Conf.Account.Username,
|
|
Password: helper.EncryptPasswd(setting.Conf.Account.Username, setting.Conf.Account.Password),
|
|
Email: setting.Conf.Account.Email,
|
|
PhoneN: setting.Conf.Account.PhoneNumber,
|
|
Address: setting.Conf.Account.Address,
|
|
CreateTime: time.Now(),
|
|
}
|
|
a.BlogName = setting.Conf.Blogger.BlogName
|
|
a.SubTitle = setting.Conf.Blogger.SubTitle
|
|
a.BeiAn = setting.Conf.Blogger.BeiAn
|
|
a.BTitle = setting.Conf.Blogger.BTitle
|
|
a.Copyright = setting.Conf.Blogger.Copyright
|
|
err = db.Insert(DB, COLLECTION_ACCOUNT, a)
|
|
generateTopic()
|
|
} else if err != nil {
|
|
logd.Fatal(err)
|
|
}
|
|
a.CH = make(chan string, 2)
|
|
a.MapArticles = make(map[string]*Article)
|
|
a.Tags = make(map[string]SortArticles)
|
|
return
|
|
}
|
|
|
|
func loadArticles() (artcs SortArticles) {
|
|
err := db.FindAll(DB, COLLECTION_ARTICLE, bson.M{"isdraft": false, "deletetime": bson.M{"$eq": time.Time{}}}, &artcs)
|
|
if err != nil {
|
|
logd.Fatal(err)
|
|
}
|
|
sort.Sort(artcs)
|
|
for i, v := range artcs {
|
|
// 渲染文章
|
|
GenerateExcerptAndRender(v)
|
|
Ei.MapArticles[v.Slug] = v
|
|
// 分析文章
|
|
if v.ID < setting.Conf.StartID {
|
|
continue
|
|
}
|
|
if i > 0 {
|
|
v.Prev = artcs[i-1]
|
|
}
|
|
if artcs[i+1].ID >= setting.Conf.StartID {
|
|
v.Next = artcs[i+1]
|
|
}
|
|
ManageTagsArticle(v, false, ADD)
|
|
ManageSeriesArticle(v, false, ADD)
|
|
ManageArchivesArticle(v, false, ADD)
|
|
}
|
|
Ei.CH <- SERIES_MD
|
|
Ei.CH <- ARCHIVE_MD
|
|
return
|
|
}
|
|
|
|
// generate series,archive markdown
|
|
func generateMarkdown() {
|
|
for {
|
|
|
|
switch typ := <-Ei.CH; typ {
|
|
case SERIES_MD:
|
|
sort.Sort(Ei.Series)
|
|
var buffer bytes.Buffer
|
|
buffer.WriteString(Ei.SeriesSay)
|
|
buffer.WriteString("\n\n")
|
|
for _, serie := range Ei.Series {
|
|
buffer.WriteString(fmt.Sprintf("### %s{#toc-%d}", serie.Name, serie.ID))
|
|
buffer.WriteString("\n")
|
|
buffer.WriteString(serie.Desc)
|
|
buffer.WriteString("\n\n")
|
|
for _, artc := range serie.Articles {
|
|
// * [标题一](/post/hello-world.html) <span class="date">(Man 02, 2006)</span>
|
|
buffer.WriteString("* [" + artc.Title + "](/post/" + artc.Slug + ".html) <span class=\"date\">(" + artc.CreateTime.Format("Jan 02, 2006") + ")</span>\n")
|
|
}
|
|
buffer.WriteByte('\n')
|
|
}
|
|
Ei.PageSeries = string(renderPage(buffer.Bytes()))
|
|
case ARCHIVE_MD:
|
|
sort.Sort(Ei.Archives)
|
|
var buffer bytes.Buffer
|
|
buffer.WriteString(Ei.ArchivesSay)
|
|
buffer.WriteString("\n\n")
|
|
for _, archive := range Ei.Archives {
|
|
buffer.WriteString(fmt.Sprintf("### %s", archive.Time.Format("2006年01月")))
|
|
buffer.WriteString("\n\n")
|
|
for _, artc := range archive.Articles {
|
|
buffer.WriteString("* [" + artc.Title + "](/post/" + artc.Slug + ".html) <span class=\"date\">(" + artc.CreateTime.Format("Jan 02, 2006") + ")</span>\n")
|
|
}
|
|
buffer.WriteByte('\n')
|
|
}
|
|
Ei.PageArchives = string(renderPage(buffer.Bytes()))
|
|
}
|
|
}
|
|
}
|
|
|
|
// init account: generate blogroll and about page
|
|
func generateTopic() {
|
|
about := &Article{
|
|
ID: db.NextVal(DB, COUNTER_ARTICLE),
|
|
Author: setting.Conf.Account.Username,
|
|
Title: "关于",
|
|
Slug: "about",
|
|
CreateTime: time.Now(),
|
|
UpdateTime: time.Now(),
|
|
}
|
|
blogroll := &Article{
|
|
ID: db.NextVal(DB, COUNTER_ARTICLE),
|
|
Author: setting.Conf.Account.Username,
|
|
Title: "友情链接",
|
|
Slug: "blogroll",
|
|
UpdateTime: time.Now(),
|
|
CreateTime: time.Now(),
|
|
}
|
|
err := db.Insert(DB, COLLECTION_ARTICLE, blogroll)
|
|
if err != nil {
|
|
logd.Fatal(err)
|
|
}
|
|
err = db.Insert(DB, COLLECTION_ARTICLE, about)
|
|
if err != nil {
|
|
logd.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// render page
|
|
func renderPage(md []byte) []byte {
|
|
renderer := blackfriday.HtmlRenderer(commonHtmlFlags, "", "")
|
|
return blackfriday.Markdown(md, renderer, commonExtensions)
|
|
}
|
|
|
|
// 文章分页
|
|
func PageList(p, n int) (prev int, next int, artcs []*Article) {
|
|
var l int
|
|
for l = len(Ei.Articles); l > 0; l-- {
|
|
if Ei.Articles[l-1].ID >= setting.Conf.StartID {
|
|
break
|
|
}
|
|
}
|
|
if l == 0 {
|
|
return 0, 0, nil
|
|
}
|
|
m := l / n
|
|
if d := l % n; d > 0 {
|
|
m++
|
|
}
|
|
if p > m {
|
|
p = m
|
|
}
|
|
if p > 1 {
|
|
prev = p - 1
|
|
}
|
|
if p < m {
|
|
next = p + 1
|
|
}
|
|
s := (p - 1) * n
|
|
e := p * n
|
|
if e > l {
|
|
e = l
|
|
}
|
|
artcs = Ei.Articles[s:e]
|
|
return
|
|
}
|
|
|
|
func ManageTagsArticle(artc *Article, s bool, dos ...string) {
|
|
for _, do := range dos {
|
|
switch do {
|
|
case ADD:
|
|
for _, tag := range artc.Tags {
|
|
Ei.Tags[tag] = append(Ei.Tags[tag], artc)
|
|
if s {
|
|
sort.Sort(Ei.Tags[tag])
|
|
}
|
|
}
|
|
case DELETE:
|
|
for _, tag := range artc.Tags {
|
|
for i, v := range Ei.Tags[tag] {
|
|
if v == artc {
|
|
Ei.Tags[tag] = append(Ei.Tags[tag][0:i], Ei.Tags[tag][i+1:]...)
|
|
if len(Ei.Tags[tag]) == 0 {
|
|
delete(Ei.Tags, tag)
|
|
}
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func ManageSeriesArticle(artc *Article, s bool, dos ...string) {
|
|
for _, do := range dos {
|
|
switch do {
|
|
case ADD:
|
|
if artc.SerieID != 0 {
|
|
for i, serie := range Ei.Series {
|
|
if serie.ID == artc.SerieID {
|
|
Ei.Series[i].Articles = append(Ei.Series[i].Articles, artc)
|
|
if s {
|
|
sort.Sort(Ei.Series[i].Articles)
|
|
Ei.CH <- SERIES_MD
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
case DELETE:
|
|
if artc.SerieID != 0 {
|
|
for i, serie := range Ei.Series {
|
|
if serie.ID == artc.SerieID {
|
|
for j, v := range serie.Articles {
|
|
if v == artc {
|
|
Ei.Series[i].Articles = append(Ei.Series[i].Articles[0:j], Ei.Series[i].Articles[j+1:]...)
|
|
Ei.CH <- SERIES_MD
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
func ManageArchivesArticle(artc *Article, s bool, dos ...string) {
|
|
for _, do := range dos {
|
|
switch do {
|
|
case ADD:
|
|
add := false
|
|
y, m, _ := artc.CreateTime.Date()
|
|
for i, archive := range Ei.Archives {
|
|
ay, am, _ := archive.Time.Date()
|
|
if y == ay && m == am {
|
|
add = true
|
|
Ei.Archives[i].Articles = append(Ei.Archives[i].Articles, artc)
|
|
if s {
|
|
sort.Sort(Ei.Archives[i].Articles)
|
|
Ei.CH <- ARCHIVE_MD
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if !add {
|
|
Ei.Archives = append(Ei.Archives, &Archive{Time: artc.CreateTime, Articles: SortArticles{artc}})
|
|
}
|
|
case DELETE:
|
|
for i, archive := range Ei.Archives {
|
|
ay, am, _ := archive.Time.Date()
|
|
if y, m, _ := artc.CreateTime.Date(); ay == y && am == m {
|
|
for j, v := range archive.Articles {
|
|
if v == artc {
|
|
Ei.Archives[i].Articles = append(Ei.Archives[i].Articles[0:j], Ei.Archives[i].Articles[j+1:]...)
|
|
Ei.CH <- ARCHIVE_MD
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// 渲染markdown操作和截取摘要操作
|
|
var reg = regexp.MustCompile(setting.Conf.Identifier)
|
|
|
|
func GenerateExcerptAndRender(artc *Article) {
|
|
index := reg.FindStringIndex(artc.Content)
|
|
if len(index) > 0 {
|
|
artc.Excerpt = artc.Content[0:index[0]]
|
|
} else {
|
|
uc := []rune(artc.Content)
|
|
length := setting.Conf.Length
|
|
if len(uc) < length {
|
|
length = len(uc)
|
|
}
|
|
artc.Excerpt = string(uc[0:length])
|
|
}
|
|
artc.Content = string(renderPage([]byte(artc.Content)))
|
|
}
|
|
|
|
// 读取草稿箱
|
|
func LoadDraft() (artcs SortArticles, err error) {
|
|
err = db.FindAll(DB, COLLECTION_ARTICLE, bson.M{"isdraft": true}, &artcs)
|
|
sort.Sort(artcs)
|
|
return
|
|
}
|
|
|
|
// 读取回收箱
|
|
func LoadTrash() (artcs SortArticles, err error) {
|
|
err = db.FindAll(DB, COLLECTION_ARTICLE, bson.M{"deletetime": bson.M{"$ne": time.Time{}}}, &artcs)
|
|
sort.Sort(artcs)
|
|
return
|
|
}
|
|
|
|
// 添加文章
|
|
func AddArticle(artc *Article) error {
|
|
// 分配ID, 占位至起始id
|
|
for {
|
|
if id := db.NextVal(DB, COUNTER_ARTICLE); id < setting.Conf.StartID {
|
|
continue
|
|
} else {
|
|
artc.ID = id
|
|
break
|
|
}
|
|
}
|
|
if !artc.IsDraft {
|
|
defer GenerateExcerptAndRender(artc)
|
|
Ei.MapArticles[artc.Slug] = artc
|
|
Ei.Articles = append([]*Article{artc}, Ei.Articles...)
|
|
sort.Sort(Ei.Articles)
|
|
AddToLinkedList(artc.ID)
|
|
ManageTagsArticle(artc, true, ADD)
|
|
ManageSeriesArticle(artc, true, ADD)
|
|
ManageArchivesArticle(artc, true, ADD)
|
|
Ei.CH <- ARCHIVE_MD
|
|
if artc.SerieID > 0 {
|
|
Ei.CH <- SERIES_MD
|
|
}
|
|
}
|
|
return db.Insert(DB, COLLECTION_ARTICLE, artc)
|
|
}
|
|
|
|
// 删除文章,移入回收箱
|
|
func DelArticles(ids ...int32) error {
|
|
lock.Lock()
|
|
defer lock.Unlock()
|
|
for _, id := range ids {
|
|
i, artc := GetArticle(id)
|
|
DelFromLinkedList(artc)
|
|
Ei.Articles = append(Ei.Articles[:i], Ei.Articles[i+1:]...)
|
|
delete(Ei.MapArticles, artc.Slug)
|
|
ManageTagsArticle(artc, false, DELETE)
|
|
ManageSeriesArticle(artc, false, DELETE)
|
|
ManageArchivesArticle(artc, false, DELETE)
|
|
err := UpdateArticle(bson.M{"id": id}, bson.M{"$set": bson.M{"deletetime": time.Now()}})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
Ei.CH <- ARCHIVE_MD
|
|
Ei.CH <- SERIES_MD
|
|
return nil
|
|
}
|
|
|
|
func DelFromLinkedList(artc *Article) {
|
|
if artc.Prev == nil && artc.Next != nil {
|
|
artc.Next.Prev = nil
|
|
} else if artc.Prev != nil && artc.Next == nil {
|
|
artc.Prev.Next = nil
|
|
} else if artc.Prev != nil && artc.Next != nil {
|
|
artc.Prev.Next = artc.Next
|
|
artc.Next.Prev = artc.Prev
|
|
}
|
|
}
|
|
|
|
func AddToLinkedList(id int32) {
|
|
i, artc := GetArticle(id)
|
|
if i == 0 && Ei.Articles[i+1].ID >= setting.Conf.StartID {
|
|
artc.Next = Ei.Articles[i+1]
|
|
Ei.Articles[i+1].Prev = artc
|
|
} else if i > 0 && Ei.Articles[i-1].ID >= setting.Conf.StartID {
|
|
artc.Prev = Ei.Articles[i-1]
|
|
if Ei.Articles[i-1].Next != nil {
|
|
artc.Next = Ei.Articles[i-1].Next
|
|
}
|
|
Ei.Articles[i-1].Next = artc
|
|
}
|
|
}
|
|
|
|
// 从缓存获取文章
|
|
func GetArticle(id int32) (int, *Article) {
|
|
for i, artc := range Ei.Articles {
|
|
if id == artc.ID {
|
|
return i, artc
|
|
}
|
|
}
|
|
return -1, nil
|
|
}
|
|
|
|
// 定时清除回收箱文章
|
|
func timer() {
|
|
delT := time.NewTicker(time.Duration(setting.Conf.Clean) * time.Hour)
|
|
for {
|
|
<-delT.C
|
|
db.Remove(DB, COLLECTION_ARTICLE, bson.M{"deletetime": bson.M{"$gt": time.Time{}, "$lt": time.Now().Add(time.Duration(setting.Conf.Trash) * time.Hour)}})
|
|
}
|
|
}
|
|
|
|
// 操作帐号字段
|
|
func UpdateAccountField(M bson.M) error {
|
|
return db.Update(DB, COLLECTION_ACCOUNT, bson.M{"username": Ei.Username}, M)
|
|
}
|
|
|
|
// 删除草稿箱或回收箱,永久删除
|
|
func RemoveArticle(id int32) error {
|
|
return db.Remove(DB, COLLECTION_ARTICLE, bson.M{"id": id})
|
|
}
|
|
|
|
// 恢复删除文章到草稿箱
|
|
func RecoverArticle(id int32) error {
|
|
return db.Update(DB, COLLECTION_ARTICLE, bson.M{"id": id}, bson.M{"$set": bson.M{"deletetime": time.Time{}, "isdraft": true}})
|
|
}
|
|
|
|
// 更新文章
|
|
func UpdateArticle(query, update interface{}) error {
|
|
return db.Update(DB, COLLECTION_ARTICLE, query, update)
|
|
}
|
|
|
|
// 编辑文档
|
|
func QueryArticle(id int32) *Article {
|
|
artc := &Article{}
|
|
if err := db.FindOne(DB, COLLECTION_ARTICLE, bson.M{"id": id}, artc); err != nil {
|
|
return nil
|
|
}
|
|
return artc
|
|
}
|
|
|
|
// 添加专题
|
|
func AddSerie(name, slug, desc string) error {
|
|
serie := &Serie{db.NextVal(DB, COUNTER_SERIE), name, slug, desc, time.Now(), nil}
|
|
Ei.Series = append(Ei.Series, serie)
|
|
sort.Sort(Ei.Series)
|
|
Ei.CH <- SERIES_MD
|
|
return UpdateAccountField(bson.M{"$addToSet": bson.M{"blogger.series": serie}})
|
|
}
|
|
|
|
// 删除专题
|
|
func DelSerie(id int32) error {
|
|
for i, serie := range Ei.Series {
|
|
if id == serie.ID {
|
|
if len(serie.Articles) > 0 {
|
|
return fmt.Errorf("请删除该专题下的所有文章")
|
|
}
|
|
err := UpdateAccountField(bson.M{"$pull": bson.M{"blogger.series": bson.M{"id": id}}})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
Ei.Series[i] = nil
|
|
Ei.Series = append(Ei.Series[:i], Ei.Series[i+1:]...)
|
|
Ei.CH <- SERIES_MD
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// 查找专题
|
|
func QuerySerie(id int32) *Serie {
|
|
for _, serie := range Ei.Series {
|
|
if serie.ID == id {
|
|
return serie
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func PageListBack(se int, kw string, draft, del bool, p, n int) (max int, artcs []*Article) {
|
|
M := bson.M{}
|
|
if draft {
|
|
M["isdraft"] = true
|
|
} else if del {
|
|
M["deletetime"] = bson.M{"$ne": time.Time{}}
|
|
} else {
|
|
M["isdraft"] = false
|
|
M["deletetime"] = bson.M{"$eq": time.Time{}}
|
|
if se > 0 {
|
|
M["serieid"] = se
|
|
}
|
|
if kw != "" {
|
|
M["title"] = bson.M{"$regex": kw, "$options": "$i"}
|
|
}
|
|
}
|
|
ms, c := db.Connect(DB, COLLECTION_ARTICLE)
|
|
defer ms.Close()
|
|
err := c.Find(M).Select(bson.M{"content": 0}).Sort("-createtime").Limit(n).Skip((p - 1) * n).All(&artcs)
|
|
if err != nil {
|
|
logd.Error(err)
|
|
}
|
|
count, err := c.Find(M).Count()
|
|
if err != nil {
|
|
logd.Error(err)
|
|
}
|
|
max = count / n
|
|
if count%n > 0 {
|
|
max++
|
|
}
|
|
return
|
|
}
|