package service
import (
"github.com/leanote/leanote/app/db"
"github.com/leanote/leanote/app/info"
. "github.com/leanote/leanote/app/lea"
"gopkg.in/mgo.v2/bson"
// "time"
// "sort"
"strconv"
"strings"
"time"
)
// blog
/*
note, notebook都可设为blog
关键是, 怎么得到blog列表? 还要分页
??? 不用新建, 直接使用notes表, 添加IsBlog字段. 新建表 blogs {NoteId, UserId, CreatedTime, IsTop(置顶)}, NoteId, UserId 为unique!!
// 设置一个note为blog
添加到blogs中
// 设置/取消notebook为blog
创建一个note时, 如果其notebookId已设为blog, 那么添加该note到blog中.
设置一个notebook为blog时, 将其下所有的note添加到blogs里 -> 更新其IsBlog为true
取消一个notebook不为blog时, 删除其下的所有note -> 更新其IsBlog为false
*/
type BlogService struct {
}
// 得到博客统计信息
// ReadNum, LikeNum, CommentNum
func (this *BlogService) GetBlogStat(noteId string) (stat info.BlogStat) {
note := noteService.GetBlogNote(noteId)
stat = info.BlogStat{note.NoteId, note.ReadNum, note.LikeNum, note.CommentNum}
return
}
// 通过id或urlTitle得到博客
func (this *BlogService) GetBlogByIdAndUrlTitle(userId string, noteIdOrUrlTitle string) (blog info.BlogItem) {
if IsObjectId(noteIdOrUrlTitle) {
return this.GetBlog(noteIdOrUrlTitle)
}
note := info.Note{}
db.GetByQ(db.Notes, bson.M{"UserId": bson.ObjectIdHex(userId), "UrlTitle": encodeValue(noteIdOrUrlTitle),
"IsBlog": true,
"IsTrash": false, "IsDeleted": false}, ¬e)
return this.GetBlogItem(note)
}
// 得到某博客具体信息
func (this *BlogService) GetBlog(noteId string) (blog info.BlogItem) {
note := noteService.GetBlogNote(noteId)
return this.GetBlogItem(note)
}
func (this *BlogService) GetBlogItem(note info.Note) (blog info.BlogItem) {
if note.NoteId == "" || !note.IsBlog {
return info.BlogItem{}
}
// 内容
noteContent := noteService.GetNoteContent(note.NoteId.Hex(), note.UserId.Hex())
// 组装成blogItem
blog = info.BlogItem{note, noteContent.Abstract, noteContent.Content, false, info.User{}}
return
}
// 得到用户共享的notebooks
// 3/19 博客不是deleted
func (this *BlogService) ListBlogNotebooks(userId string) []info.Notebook {
notebooks := []info.Notebook{}
orQ := []bson.M{
bson.M{"IsDeleted": false},
bson.M{"IsDeleted": bson.M{"$exists": false}},
}
db.ListByQ(db.Notebooks, bson.M{"UserId": bson.ObjectIdHex(userId), "IsBlog": true, "$or": orQ}, ¬ebooks)
return notebooks
}
// 博客列表
// userId 表示谁的blog
func (this *BlogService) ListBlogs(userId, notebookId string, page, pageSize int, sortField string, isAsc bool) (info.Page, []info.BlogItem) {
count, notes := noteService.ListNotes(userId, notebookId, false, page, pageSize, sortField, isAsc, true)
if notes == nil || len(notes) == 0 {
return info.Page{}, nil
}
// 得到content, 并且每个都要substring
noteIds := make([]bson.ObjectId, len(notes))
for i, note := range notes {
noteIds[i] = note.NoteId
}
// 直接得到noteContents表的abstract
// 这里可能是乱序的
noteContents := noteService.ListNoteAbstractsByNoteIds(noteIds) // 返回[info.NoteContent]
noteContentsMap := make(map[bson.ObjectId]info.NoteContent, len(noteContents))
for _, noteContent := range noteContents {
noteContentsMap[noteContent.NoteId] = noteContent
}
// 组装成blogItem
// 按照notes的顺序
blogs := make([]info.BlogItem, len(noteIds))
for i, note := range notes {
hasMore := true
var content string
var abstract string
if noteContent, ok := noteContentsMap[note.NoteId]; ok {
abstract = noteContent.Abstract
content = noteContent.Content
}
blogs[i] = info.BlogItem{note, abstract, content, hasMore, info.User{}}
}
pageInfo := info.NewPage(page, pageSize, count, nil)
return pageInfo, blogs
}
// 得到博客的标签, 那得先得到所有博客, 比较慢
/*
[
{Tag:xxx, Count: 32}
]
*/
func (this *BlogService) GetBlogTags(userId string) []info.TagCount {
// 得到所有博客
tagCounts := []info.TagCount{}
// tag不能为空
query := bson.M{"UserId": bson.ObjectIdHex(userId), "IsBlog": true, "Tag": bson.M{"$ne": ""}}
db.TagCounts.Find(query).Sort("-Count").All(&tagCounts)
return tagCounts
}
// 重新计算博客的标签
// 在设置设置/取消为博客时调用
func (this *BlogService) ReCountBlogTags(userId string) bool {
// 得到所有博客
notes := []info.Note{}
userIdO := bson.ObjectIdHex(userId)
query := bson.M{"UserId": userIdO, "IsTrash": false, "IsDeleted": false, "IsBlog": true}
db.ListByQWithFields(db.Notes, query, []string{"Tags"}, ¬es)
db.DeleteAll(db.TagCounts, bson.M{"UserId": userIdO, "IsBlog": true})
if notes == nil || len(notes) == 0 {
return true
}
// 统计所有的Tags和数目
tagsCount := map[string]int{}
for _, note := range notes {
tags := note.Tags
if tags != nil && len(tags) > 0 {
for _, tag := range tags {
count := tagsCount[tag]
count++
tagsCount[tag] = count
}
}
}
// 一个个插入
for tag, count := range tagsCount {
db.Insert(db.TagCounts,
info.TagCount{UserId: userIdO, IsBlog: true, Tag: tag, Count: count})
}
return true
}
// 归档博客
/*
数据: 按年汇总
[
archive1,
archive2,
]
archive的数据类型是
{
Year: 2014
Posts: []
}
*/
func (this *BlogService) ListBlogsArchive(userId, notebookId string, year, month int, sortField string, isAsc bool) []info.Archive {
// _, notes := noteService.ListNotes(userId, notebookId, false, 1, 99999, sortField, isAsc, true);
q := bson.M{"UserId": bson.ObjectIdHex(userId), "IsBlog": true, "IsTrash": false, "IsDeleted": false}
if notebookId != "" {
q["NotebookId"] = bson.ObjectIdHex(notebookId)
}
if year > 0 {
now := time.Now()
nextYear := year
nextMonth := month
if month == 0 {
month = 1
nextYear = year + 1
nextMonth = month
} else if month >= 12 {
month = 12
nextYear = year + 1
nextMonth = 1
} else { // month 在1-12之间
nextMonth = month + 1
}
leftT := time.Date(year, time.Month(month), 1, 0, 0, 0, 0, now.Location())
rightT := time.Date(nextYear, time.Month(nextMonth), 1, 0, 0, 0, 0, now.Location())
if sortField == "CreatedTime" || sortField == "UpdatedTime" {
q[sortField] = bson.M{"$gte": leftT, "$lt": rightT}
} else {
q["PublicTime"] = bson.M{"$gte": leftT, "$lt": rightT}
}
}
sorter := sortField
if !isAsc {
sorter = "-" + sortField
}
notes := []info.Note{}
db.Notes.Find(q).Sort(sorter).All(¬es)
if notes == nil || len(notes) == 0 {
return nil
}
arcs := []info.Archive{}
// 按年汇总
arcsMap := map[int]info.Archive{}
// 按月汇总
arcsMonth := []info.ArchiveMonth{}
var t time.Time
var arc info.Archive
everYear := 0
for _, note := range notes {
if sortField == "PublicTime" {
t = note.PublicTime
} else if sortField == "CreatedTime" {
t = note.CreatedTime
} else {
t = note.UpdatedTime
}
year := t.Year()
month := int(t.Month())
if everYear == 0 {
everYear = year
}
if everYear != year {
yearArc := arcsMap[everYear]
yearArc.MonthAchives = arcsMonth
arcs = append(arcs, yearArc)
everYear = year
// 新的一年
arcsMonth = []info.ArchiveMonth{}
}
if arcT, ok := arcsMap[year]; ok {
arc = arcT
} else {
arc = info.Archive{Year: year, Posts: []*info.Post{}}
}
pt := this.FixNote(note)
p := &pt
arc.Posts = append(arc.Posts, p)
arcsMap[year] = arc
// month
lm := len(arcsMonth)
if lm == 0 || arcsMonth[lm-1].Month != month {
arcsMonth = append(arcsMonth, info.ArchiveMonth{month, []*info.Post{p}})
} else {
arcsMonth[lm-1].Posts = append(arcsMonth[lm-1].Posts, p)
}
}
// 最后一个
if everYear > 0 {
yearArc := arcsMap[everYear]
yearArc.MonthAchives = arcsMonth
arcs = append(arcs, yearArc)
}
return arcs
}
// 根据tag搜索博客
func (this *BlogService) SearchBlogByTags(tags []string, userId string, pageNumber, pageSize int, sortField string, isAsc bool) (pageInfo info.Page, blogs []info.BlogItem) {
notes := []info.Note{}
skipNum, sortFieldR := parsePageAndSort(pageNumber, pageSize, sortField, isAsc)
// 不是trash的
query := bson.M{"UserId": bson.ObjectIdHex(userId),
"IsTrash": false,
"IsDeleted": false,
"IsBlog": true,
"Tags": bson.M{"$all": tags}}
q := db.Notes.Find(query)
// 总记录数
count, _ := q.Count()
if count == 0 {
return
}
q.Sort(sortFieldR).
Skip(skipNum).
Limit(pageSize).
All(¬es)
blogs = this.notes2BlogItems(notes)
pageInfo = info.NewPage(pageNumber, pageSize, count, nil)
return
}
func (this *BlogService) notes2BlogItems(notes []info.Note) []info.BlogItem {
// 得到content, 并且每个都要substring
noteIds := make([]bson.ObjectId, len(notes))
for i, note := range notes {
noteIds[i] = note.NoteId
}
// 直接得到noteContents表的abstract
// 这里可能是乱序的
noteContents := noteService.ListNoteContentByNoteIds(noteIds) // 返回[info.NoteContent]
noteContentsMap := make(map[bson.ObjectId]info.NoteContent, len(noteContents))
for _, noteContent := range noteContents {
noteContentsMap[noteContent.NoteId] = noteContent
}
// 组装成blogItem
// 按照notes的顺序
blogs := make([]info.BlogItem, len(noteIds))
for i, note := range notes {
hasMore := true
var content, abstract string
if noteContent, ok := noteContentsMap[note.NoteId]; ok {
abstract = noteContent.Abstract
content = noteContent.Content
}
blogs[i] = info.BlogItem{note, abstract, content, hasMore, info.User{}}
}
return blogs
}
func (this *BlogService) SearchBlog(key, userId string, page, pageSize int, sortField string, isAsc bool) (info.Page, []info.BlogItem) {
count, notes := noteService.SearchNote(key, userId, page, pageSize, sortField, isAsc, true)
if notes == nil || len(notes) == 0 {
return info.Page{}, nil
}
blogs := this.notes2BlogItems(notes)
pageInfo := info.NewPage(page, pageSize, count, nil)
return pageInfo, blogs
}
// 上一篇文章, 下一篇文章
// sorterField, baseTime是基准, sorterField=PublicTime, title
// isAsc是用户自定义的排序方式
func (this *BlogService) PreNextBlog(userId string, sorterField string, isAsc bool, noteId string, baseTime interface{}) (info.Post, info.Post) {
userIdO := bson.ObjectIdHex(userId)
var sortFieldT1, sortFieldT2 bson.M
var sortFieldR1, sortFieldR2 string
if !isAsc {
// 降序
/*
------- pre
----- now
--- next
--
*/
// 上一篇时间要比它大, 找最小的
sortFieldT1 = bson.M{"$gte": baseTime} // 为什么要相等, 因为将notebook发布成博客, 会统一修改note的publicTime, 此时所有notes都一样
sortFieldR1 = sorterField
// 下一篇时间要比它小
sortFieldT2 = bson.M{"$lte": baseTime}
sortFieldR2 = "-" + sorterField
} else {
// 升序
/*
--- pre
----- now
------- next
---------
*/
// 上一篇要比它小, 找最大的
sortFieldT1 = bson.M{"$lte": baseTime}
sortFieldR1 = "-" + sorterField
// 下一篇, 找最小的
sortFieldT2 = bson.M{"$gte": baseTime}
sortFieldR2 = sorterField
}
// 1
// 上一篇, 比基时间要小, 但是是最后一篇, 所以是降序
note := info.Note{}
query := bson.M{"UserId": userIdO,
"IsTrash": false,
"IsDeleted": false,
"IsBlog": true,
"_id": bson.M{"$ne": bson.ObjectIdHex(noteId)},
sorterField: sortFieldT1,
}
q := db.Notes.Find(query)
q.Sort(sortFieldR1).Limit(1).One(¬e)
// 下一篇, 比基时间要大, 但是是第一篇, 所以是升序
if note.NoteId != "" {
query["_id"] = bson.M{"$nin": []bson.ObjectId{bson.ObjectIdHex(noteId), note.NoteId}}
}
note2 := info.Note{}
query[sorterField] = sortFieldT2
// Log(isAsc)
// LogJ(query)
// Log(sortFieldR2)
q = db.Notes.Find(query)
q.Sort(sortFieldR2).Limit(1).One(¬e2)
return this.FixNote(note), this.FixNote(note2)
}
//-------
// p
// 平台 lea+
// 博客列表
func (this *BlogService) ListAllBlogs(userId, tag string, keywords string, isRecommend bool, page, pageSize int, sorterField string, isAsc bool) (info.Page, []info.BlogItem) {
pageInfo := info.Page{CurPage: page}
notes := []info.Note{}
skipNum, sortFieldR := parsePageAndSort(page, pageSize, sorterField, isAsc)
// 不是trash的
query := bson.M{"IsTrash": false, "IsDeleted": false, "IsBlog": true, "Title": bson.M{"$ne": "欢迎来到leanote!"}}
if tag != "" {
query["Tags"] = bson.M{"$in": []string{tag}}
}
if userId != "" {
query["UserId"] = bson.ObjectIdHex(userId)
}
// 不是demo的博客
demoUserId := configService.GetGlobalStringConfig("demoUserId")
if userId == "" && demoUserId != "" {
query["UserId"] = bson.M{"$ne": bson.ObjectIdHex(demoUserId)}
}
if isRecommend {
query["IsRecommend"] = isRecommend
}
if keywords != "" {
query["Title"] = bson.M{"$regex": bson.RegEx{".*?" + keywords + ".*", "i"}}
}
q := db.Notes.Find(query)
// 总记录数
count, _ := q.Count()
q.Sort(sortFieldR).
Skip(skipNum).
Limit(pageSize).
All(¬es)
if notes == nil || len(notes) == 0 {
return pageInfo, nil
}
// 得到content, 并且每个都要substring
noteIds := make([]bson.ObjectId, len(notes))
userIds := make([]bson.ObjectId, len(notes))
for i, note := range notes {
noteIds[i] = note.NoteId
userIds[i] = note.UserId
}
// 可以不要的
// 直接得到noteContents表的abstract
// 这里可能是乱序的
/*
noteContents := noteService.ListNoteAbstractsByNoteIds(noteIds) // 返回[info.NoteContent]
noteContentsMap := make(map[bson.ObjectId]info.NoteContent, len(noteContents))
for _, noteContent := range noteContents {
noteContentsMap[noteContent.NoteId] = noteContent
}
*/
// 得到用户信息
userMap := userService.MapUserInfoAndBlogInfosByUserIds(userIds)
// 组装成blogItem
// 按照notes的顺序
blogs := make([]info.BlogItem, len(noteIds))
for i, note := range notes {
hasMore := true
var content string
/*
if noteContent, ok := noteContentsMap[note.NoteId]; ok {
content = noteContent.Abstract
}
*/
if len(note.Tags) == 1 && note.Tags[0] == "" {
note.Tags = nil
}
blogs[i] = info.BlogItem{note, "", content, hasMore, userMap[note.UserId]}
}
pageInfo = info.NewPage(page, pageSize, count, nil)
return pageInfo, blogs
}
//------------------------
// 博客设置
func (this *BlogService) fixUserBlog(userBlog *info.UserBlog) {
// Logo路径问题, 有些有http: 有些没有
if userBlog.Logo != "" && !strings.HasPrefix(userBlog.Logo, "http") {
userBlog.Logo = strings.Trim(userBlog.Logo, "/")
userBlog.Logo = "/" + userBlog.Logo
}
if userBlog.SortField == "" {
userBlog.SortField = "PublicTime"
}
if userBlog.PerPageSize <= 0 {
userBlog.PerPageSize = 10
}
// themePath
if userBlog.Style == "" {
userBlog.Style = defaultStyle
}
if userBlog.ThemeId == "" {
userBlog.ThemePath = themeService.GetDefaultThemePath(userBlog.Style)
} else {
userBlog.ThemePath = themeService.GetThemePath(userBlog.UserId.Hex(), userBlog.ThemeId.Hex())
}
}
func (this *BlogService) GetUserBlog(userId string) info.UserBlog {
userBlog := info.UserBlog{}
db.Get(db.UserBlogs, userId, &userBlog)
this.fixUserBlog(&userBlog)
return userBlog
}
// 修改之
func (this *BlogService) UpdateUserBlog(userBlog info.UserBlog) bool {
return db.Upsert(db.UserBlogs, bson.M{"_id": userBlog.UserId}, userBlog)
}
// 修改之UserBlogBase
func (this *BlogService) UpdateUserBlogBase(userId string, userBlog info.UserBlogBase) bool {
ok := db.UpdateByQMap(db.UserBlogs, bson.M{"_id": bson.ObjectIdHex(userId)}, userBlog)
return ok
}
func (this *BlogService) UpdateUserBlogComment(userId string, userBlog info.UserBlogComment) bool {
return db.UpdateByQMap(db.UserBlogs, bson.M{"_id": bson.ObjectIdHex(userId)}, userBlog)
}
func (this *BlogService) UpdateUserBlogStyle(userId string, userBlog info.UserBlogStyle) bool {
return db.UpdateByQMap(db.UserBlogs, bson.M{"_id": bson.ObjectIdHex(userId)}, userBlog)
}
// 分页与排序
func (this *BlogService) UpdateUserBlogPaging(userId string, perPageSize int, sortField string, isAsc bool) (ok bool, msg string) {
if ok, msg = Vds(map[string]string{"perPageSize": strconv.Itoa(perPageSize), "sortField": sortField}); !ok {
return
}
ok = db.UpdateByQMap(db.UserBlogs, bson.M{"_id": bson.ObjectIdHex(userId)},
bson.M{"PerPageSize": perPageSize, "SortField": sortField, "IsAsc": isAsc})
return
}
func (this *BlogService) GetUserBlogBySubDomain(subDomain string) info.UserBlog {
blogUser := info.UserBlog{}
db.GetByQ(db.UserBlogs, bson.M{"SubDomain": subDomain}, &blogUser)
this.fixUserBlog(&blogUser)
return blogUser
}
func (this *BlogService) GetUserBlogByDomain(domain string) info.UserBlog {
blogUser := info.UserBlog{}
db.GetByQ(db.UserBlogs, bson.M{"Domain": domain}, &blogUser)
this.fixUserBlog(&blogUser)
return blogUser
}
//---------------------
// 后台管理
// 推荐博客
func (this *BlogService) SetRecommend(noteId string, isRecommend bool) bool {
data := bson.M{"IsRecommend": isRecommend}
if isRecommend {
data["RecommendTime"] = time.Now()
}
return db.UpdateByQMap(db.Notes, bson.M{"_id": bson.ObjectIdHex(noteId), "IsBlog": true}, data)
}
//----------------------
// 博客社交, 评论
// 返回所有liked用户, bool是否还有
func (this *BlogService) ListLikedUsers(noteId string, isAll bool) ([]info.UserAndBlog, bool) {
// 默认前5
pageSize := 5
skipNum, sortFieldR := parsePageAndSort(1, pageSize, "CreatedTime", false)
likes := []info.BlogLike{}
query := bson.M{"NoteId": bson.ObjectIdHex(noteId)}
q := db.BlogLikes.Find(query)
// 总记录数
count, _ := q.Count()
if count == 0 {
return nil, false
}
if isAll {
q.Sort(sortFieldR).Skip(skipNum).Limit(pageSize).All(&likes)
} else {
q.Sort(sortFieldR).All(&likes)
}
// 得到所有userIds
userIds := make([]bson.ObjectId, len(likes))
for i, like := range likes {
userIds[i] = like.UserId
}
// 得到用户信息
userMap := userService.MapUserAndBlogByUserIds(userIds)
users := make([]info.UserAndBlog, len(likes))
for i, like := range likes {
users[i] = userMap[like.UserId.Hex()]
}
return users, count > pageSize
}
func (this *BlogService) IsILikeIt(noteId, userId string) bool {
if userId == "" {
return false
}
if db.Has(db.BlogLikes, bson.M{"NoteId": bson.ObjectIdHex(noteId), "UserId": bson.ObjectIdHex(userId)}) {
return true
}
return false
}
// 阅读次数统计+1
func (this *BlogService) IncReadNum(noteId string) bool {
note := noteService.GetNoteById(noteId)
if note.IsBlog {
return db.Update(db.Notes, bson.M{"_id": bson.ObjectIdHex(noteId)}, bson.M{"$inc": bson.M{"ReadNum": 1}})
}
return false
}
// 点赞
// retun ok , isLike
func (this *BlogService) LikeBlog(noteId, userId string) (ok bool, isLike bool) {
ok = false
isLike = false
if noteId == "" || userId == "" {
return
}
// 判断是否点过赞, 如果点过那么取消点赞
note := noteService.GetNoteById(noteId)
if !note.IsBlog /*|| note.UserId.Hex() == userId */ {
return
}
noteIdO := bson.ObjectIdHex(noteId)
userIdO := bson.ObjectIdHex(userId)
var n int
if !db.Has(db.BlogLikes, bson.M{"NoteId": noteIdO, "UserId": userIdO}) {
n = 1
// 添加之
db.Insert(db.BlogLikes, info.BlogLike{LikeId: bson.NewObjectId(), NoteId: noteIdO, UserId: userIdO, CreatedTime: time.Now()})
isLike = true
} else {
// 已点过, 那么删除之
n = -1
db.Delete(db.BlogLikes, bson.M{"NoteId": noteIdO, "UserId": userIdO})
isLike = false
}
ok = db.Update(db.Notes, bson.M{"_id": noteIdO}, bson.M{"$inc": bson.M{"LikeNum": n}})
return
}
// 评论
// 在noteId博客下userId 给toUserId评论content
// commentId可为空(针对某条评论评论)
func (this *BlogService) Comment(noteId, toCommentId, userId, content string) (bool, info.BlogComment) {
var comment info.BlogComment
if content == "" {
return false, comment
}
note := noteService.GetNoteById(noteId)
if !note.IsBlog {
return false, comment
}
comment = info.BlogComment{CommentId: bson.NewObjectId(),
NoteId: bson.ObjectIdHex(noteId),
UserId: bson.ObjectIdHex(userId),
Content: content,
CreatedTime: time.Now(),
}
var comment2 = info.BlogComment{}
if toCommentId != "" {
comment2 = info.BlogComment{}
db.Get(db.BlogComments, toCommentId, &comment2)
if comment2.CommentId != "" {
comment.ToCommentId = comment2.CommentId
comment.ToUserId = comment2.UserId
}
} else {
// comment.ToUserId = note.UserId
}
ok := db.Insert(db.BlogComments, comment)
if ok {
// 评论+1
db.Update(db.Notes, bson.M{"_id": bson.ObjectIdHex(noteId)}, bson.M{"$inc": bson.M{"CommentNum": 1}})
}
if userId != note.UserId.Hex() || toCommentId != "" {
go func() {
this.sendEmail(note, comment2, userId, content)
}()
}
return ok, comment
}
// 发送email
func (this *BlogService) sendEmail(note info.Note, comment info.BlogComment, userId, content string) {
emailService.SendCommentEmail(note, comment, userId, content)
/*
toUserId := note.UserId.Hex()
// title := "评论提醒"
// 表示回复回复的内容, 那么发送给之前回复的
if comment.CommentId != "" {
toUserId = comment.UserId.Hex()
}
toUserInfo := userService.GetUserInfo(toUserId)
sendUserInfo := userService.GetUserInfo(userId)
subject := note.Title + " 收到 " + sendUserInfo.Username + " 的评论";
if comment.CommentId != "" {
subject = "您在 " + note.Title + " 发表的评论收到 " + sendUserInfo.Username;
if userId == note.UserId.Hex() {
subject += "(作者)";
}
subject += " 的评论";
}
body := "{header}评论内容:
" + content + ""; href := "http://"+ configService.GetBlogDomain() + "/view/" + note.NoteId.Hex() body += "