Files
eiblog/db.go

628 lines
15 KiB
Go

// Package main provides ...
package main
import (
"bytes"
"fmt"
"regexp"
"sort"
"strings"
"sync"
"time"
"github.com/eiblog/blackfriday"
"github.com/eiblog/eiblog/setting"
"github.com/eiblog/utils/logd"
"github.com/eiblog/utils/mgo"
)
// 数据库及表名
const (
DB = "eiblog"
COLLECTION_ACCOUNT = "account"
COLLECTION_ARTICLE = "article"
COUNTER_SERIE = "serie"
COUNTER_ARTICLE = "article"
SERIES_MD = "series_md"
ARCHIVE_MD = "archive_md"
ADD = "add"
DELETE = "delete"
)
// blackfriday 配置
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() {
// 数据库加索引
err := mgo.Index(DB, COLLECTION_ACCOUNT, []string{"username"})
if err != nil {
logd.Fatal(err)
}
err = mgo.Index(DB, COLLECTION_ARTICLE, []string{"id"})
if err != nil {
logd.Fatal(err)
}
err = mgo.Index(DB, COLLECTION_ARTICLE, []string{"slug"})
if err != nil {
logd.Fatal(err)
}
// 读取帐号信息
Ei = loadAccount()
// 获取文章数据
Ei.Articles = loadArticles()
// 生成markdown文档
go generateMarkdown()
// 启动定时器
go timer()
// 获取评论数量
go PostsCount()
}
// 读取或初始化帐号信息
func loadAccount() (a *Account) {
a = &Account{}
err := mgo.FindOne(DB, COLLECTION_ACCOUNT, mgo.M{"username": setting.Conf.Account.Username}, a)
// 初始化用户数据
if err == mgo.ErrNotFound {
a = &Account{
Username: setting.Conf.Account.Username,
Password: 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 = mgo.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 := mgo.FindAll(DB, COLLECTION_ARTICLE, mgo.M{"isdraft": false, "deletetime": mgo.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.General.StartID {
continue
}
if i > 0 {
v.Prev = artcs[i-1]
}
if artcs[i+1].ID >= setting.Conf.General.StartID {
v.Next = artcs[i+1]
}
upArticle(v, false)
}
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 {
//eg. * [标题一](/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 + "\n")
var (
currentYear string
gt12Month = len(Ei.Archives) > 12
)
for _, archive := range Ei.Archives {
if gt12Month {
year := archive.Time.Format("2006年")
if currentYear != year {
currentYear = year
buffer.WriteString(fmt.Sprintf("\n### %s\n\n", archive.Time.Format("2006年")))
}
} else {
buffer.WriteString(fmt.Sprintf("\n### %s\n\n", archive.Time.Format("2006年2月")))
}
for i, artc := range archive.Articles {
if i == 0 && gt12Month {
buffer.WriteString("* *[" + artc.Title + "](/post/" + artc.Slug +
".html)* <span class=\"date\">(" + artc.CreateTime.Format("Jan 02, 2006") + ")</span>\n")
} else {
buffer.WriteString("* [" + artc.Title + "](/post/" + artc.Slug +
".html) <span class=\"date\">(" + artc.CreateTime.Format("Jan 02, 2006") + ")</span>\n")
}
}
}
Ei.PageArchives = string(renderPage(buffer.Bytes()))
}
}
}
// init account: generate blogroll and about page
func generateTopic() {
about := &Article{
ID: mgo.NextVal(DB, COUNTER_ARTICLE),
Author: setting.Conf.Account.Username,
Title: "关于",
Slug: "about",
CreateTime: time.Time{},
UpdateTime: time.Time{},
}
blogroll := &Article{
ID: mgo.NextVal(DB, COUNTER_ARTICLE),
Author: setting.Conf.Account.Username,
Title: "友情链接",
Slug: "blogroll",
CreateTime: time.Time{},
UpdateTime: time.Time{},
}
err := mgo.Insert(DB, COLLECTION_ARTICLE, blogroll)
if err != nil {
logd.Fatal(err)
}
err = mgo.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.General.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
}
// 渲染markdown操作和截取摘要操作
var reg = regexp.MustCompile(setting.Conf.General.Identifier)
// header
var regH = regexp.MustCompile("</nav></div>")
func GenerateExcerptAndRender(artc *Article) {
if strings.HasPrefix(artc.Content, setting.Conf.General.DescPrefix) {
index := strings.Index(artc.Content, "\r\n")
artc.Desc = IgnoreHtmlTag(artc.Content[len(setting.Conf.General.DescPrefix):index])
artc.Content = artc.Content[index:]
}
// 查找目录
content := renderPage([]byte(artc.Content))
index := regH.FindIndex(content)
if index != nil {
artc.Header = string(content[0:index[1]])
artc.Content = string(content[index[1]:])
} else {
artc.Content = string(content)
}
index = reg.FindStringIndex(artc.Content)
if index != nil {
artc.Excerpt = IgnoreHtmlTag(artc.Content[0:index[0]])
} else {
uc := []rune(artc.Content)
length := setting.Conf.General.Length
if len(uc) < length {
length = len(uc)
}
artc.Excerpt = IgnoreHtmlTag(string(uc[0:length]))
}
}
// 读取草稿箱
func LoadDraft() (artcs SortArticles, err error) {
err = mgo.FindAll(DB, COLLECTION_ARTICLE, mgo.M{"isdraft": true}, &artcs)
sort.Sort(artcs)
return
}
// 读取回收箱
func LoadTrash() (artcs SortArticles, err error) {
err = mgo.FindAll(DB, COLLECTION_ARTICLE, mgo.M{"deletetime": mgo.M{"$ne": time.Time{}}}, &artcs)
sort.Sort(artcs)
return
}
// 添加文章到tag、serie、archive
func upArticle(artc *Article, needSort bool) {
// tag
for _, tag := range artc.Tags {
Ei.Tags[tag] = append(Ei.Tags[tag], artc)
if needSort {
sort.Sort(Ei.Tags[tag])
}
}
// serie
for i, serie := range Ei.Series {
if serie.ID == artc.SerieID {
Ei.Series[i].Articles = append(Ei.Series[i].Articles, artc)
if needSort {
sort.Sort(Ei.Series[i].Articles)
Ei.CH <- SERIES_MD
}
break
}
}
// archive
y, m, _ := artc.CreateTime.Date()
for i, archive := range Ei.Archives {
if ay, am, _ := archive.Time.Date(); y == ay && m == am {
Ei.Archives[i].Articles = append(Ei.Archives[i].Articles, artc)
if needSort {
sort.Sort(Ei.Archives[i].Articles)
Ei.CH <- ARCHIVE_MD
}
return
}
}
Ei.Archives = append(Ei.Archives, &Archive{Time: artc.CreateTime,
Articles: SortArticles{artc}})
if needSort {
Ei.CH <- ARCHIVE_MD
}
}
// 删除文章从tag、serie、archive
func dropArticle(artc *Article) {
// tag
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)
}
}
}
}
// serie
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
break
}
}
}
}
// archive
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:]...)
if len(Ei.Archives[i].Articles) == 0 {
Ei.Archives = append(Ei.Archives[:i], Ei.Archives[i+1:]...)
}
Ei.CH <- ARCHIVE_MD
break
}
}
}
}
}
// 替换文章
func ReplaceArticle(oldArtc *Article, newArtc *Article) {
if oldArtc != nil {
i, artc := GetArticle(oldArtc.ID)
DelFromLinkedList(artc)
Ei.Articles = append(Ei.Articles[:i], Ei.Articles[i+1:]...)
delete(Ei.MapArticles, artc.Slug)
dropArticle(oldArtc)
}
Ei.MapArticles[newArtc.Slug] = newArtc
Ei.Articles = append(Ei.Articles, newArtc)
sort.Sort(Ei.Articles)
GenerateExcerptAndRender(newArtc)
AddToLinkedList(newArtc.ID)
upArticle(newArtc, true)
}
// 添加文章
func AddArticle(artc *Article) error {
// 分配ID, 占位至起始id
for {
if id := mgo.NextVal(DB, COUNTER_ARTICLE); id < setting.Conf.General.StartID {
continue
} else {
artc.ID = id
break
}
}
err := mgo.Insert(DB, COLLECTION_ARTICLE, artc)
if err != nil {
return err
}
// 正式发布文章
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)
upArticle(artc, true)
}
return nil
}
// 删除文章,移入回收箱
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)
err := UpdateArticle(mgo.M{"id": id}, mgo.M{"$set": mgo.M{"deletetime": time.Now()}})
if err != nil {
return err
}
dropArticle(artc)
}
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.General.StartID {
artc.Next = Ei.Articles[i+1]
Ei.Articles[i+1].Prev = artc
} else if i > 0 && Ei.Articles[i-1].ID >= setting.Conf.General.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.Prev = artc
}
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.General.Clean) * time.Hour)
for {
<-delT.C
mgo.Remove(DB, COLLECTION_ARTICLE, mgo.M{"deletetime": mgo.M{"$gt": time.Time{},
"$lt": time.Now().Add(time.Duration(setting.Conf.General.Trash) * time.Hour)}})
}
}
// 操作帐号字段
func UpdateAccountField(M mgo.M) error {
return mgo.Update(DB, COLLECTION_ACCOUNT, mgo.M{"username": Ei.Username}, M)
}
// 删除草稿箱或回收箱,永久删除
func RemoveArticle(id int32) error {
return mgo.Remove(DB, COLLECTION_ARTICLE, mgo.M{"id": id})
}
// 恢复删除文章到草稿箱
func RecoverArticle(id int32) error {
return mgo.Update(DB, COLLECTION_ARTICLE, mgo.M{"id": id},
mgo.M{"$set": mgo.M{"deletetime": time.Time{}, "isdraft": true}})
}
// 更新文章
func UpdateArticle(query, update interface{}) error {
return mgo.Update(DB, COLLECTION_ARTICLE, query, update)
}
// 编辑文档
func QueryArticle(id int32) *Article {
artc := &Article{}
if err := mgo.FindOne(DB, COLLECTION_ARTICLE, mgo.M{"id": id}, artc); err != nil {
return nil
}
return artc
}
// 添加专题
func AddSerie(name, slug, desc string) error {
serie := &Serie{mgo.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(mgo.M{"$addToSet": mgo.M{"blogger.series": serie}})
}
// 更新专题
func UpdateSerie(serie *Serie) error {
Ei.CH <- SERIES_MD
return mgo.Update(DB, COLLECTION_ACCOUNT, mgo.M{"username": Ei.Username,
"blogger.series.id": serie.ID}, mgo.M{"$set": mgo.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(mgo.M{"$pull": mgo.M{"blogger.series": mgo.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 := mgo.M{}
if draft {
M["isdraft"] = true
} else if del {
M["deletetime"] = mgo.M{"$ne": time.Time{}}
} else {
M["isdraft"] = false
M["deletetime"] = mgo.M{"$eq": time.Time{}}
if se > 0 {
M["serieid"] = se
}
if kw != "" {
M["title"] = mgo.M{"$regex": kw, "$options": "$i"}
}
}
ms, c := mgo.Connect(DB, COLLECTION_ARTICLE)
defer ms.Close()
err := c.Find(M).Select(mgo.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
}